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 | |
4 | #include <limits> |
5 | |
6 | #include <engine/graphics.h> |
7 | #include <engine/shared/config.h> |
8 | #include <engine/textrender.h> |
9 | |
10 | #include <game/generated/protocol.h> |
11 | |
12 | #include <game/client/animstate.h> |
13 | #include <game/client/render.h> |
14 | #include <game/localization.h> |
15 | |
16 | #include "camera.h" |
17 | #include "spectator.h" |
18 | |
19 | #include <game/client/gameclient.h> |
20 | |
21 | bool CSpectator::CanChangeSpectator() |
22 | { |
23 | // Don't change SpectatorId when not spectating |
24 | return m_pClient->m_Snap.m_SpecInfo.m_Active; |
25 | } |
26 | |
27 | void CSpectator::SpectateNext(bool Reverse) |
28 | { |
29 | int CurIndex = -1; |
30 | const CNetObj_PlayerInfo **paPlayerInfos = m_pClient->m_Snap.m_apInfoByDDTeamName; |
31 | |
32 | // m_SpectatorId may be uninitialized if m_Active is false |
33 | if(m_pClient->m_Snap.m_SpecInfo.m_Active) |
34 | { |
35 | for(int i = 0; i < MAX_CLIENTS; i++) |
36 | { |
37 | if(paPlayerInfos[i] && paPlayerInfos[i]->m_ClientId == m_pClient->m_Snap.m_SpecInfo.m_SpectatorId) |
38 | { |
39 | CurIndex = i; |
40 | break; |
41 | } |
42 | } |
43 | } |
44 | |
45 | int Start; |
46 | if(CurIndex != -1) |
47 | { |
48 | if(Reverse) |
49 | Start = CurIndex - 1; |
50 | else |
51 | Start = CurIndex + 1; |
52 | } |
53 | else |
54 | { |
55 | if(Reverse) |
56 | Start = -1; |
57 | else |
58 | Start = 0; |
59 | } |
60 | |
61 | int Increment = Reverse ? -1 : 1; |
62 | |
63 | for(int i = 0; i < MAX_CLIENTS; i++) |
64 | { |
65 | int PlayerIndex = (Start + i * Increment) % MAX_CLIENTS; |
66 | // % in C++ takes the sign of the dividend, not divisor |
67 | if(PlayerIndex < 0) |
68 | PlayerIndex += MAX_CLIENTS; |
69 | |
70 | const CNetObj_PlayerInfo *pPlayerInfo = paPlayerInfos[PlayerIndex]; |
71 | if(pPlayerInfo && pPlayerInfo->m_Team != TEAM_SPECTATORS) |
72 | { |
73 | Spectate(SpectatorId: pPlayerInfo->m_ClientId); |
74 | break; |
75 | } |
76 | } |
77 | } |
78 | |
79 | void CSpectator::ConKeySpectator(IConsole::IResult *pResult, void *pUserData) |
80 | { |
81 | CSpectator *pSelf = (CSpectator *)pUserData; |
82 | |
83 | if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK) |
84 | pSelf->m_Active = pResult->GetInteger(Index: 0) != 0; |
85 | else |
86 | pSelf->m_Active = false; |
87 | } |
88 | |
89 | void CSpectator::ConSpectate(IConsole::IResult *pResult, void *pUserData) |
90 | { |
91 | CSpectator *pSelf = (CSpectator *)pUserData; |
92 | if(!pSelf->CanChangeSpectator()) |
93 | return; |
94 | |
95 | pSelf->Spectate(SpectatorId: pResult->GetInteger(Index: 0)); |
96 | } |
97 | |
98 | void CSpectator::ConSpectateNext(IConsole::IResult *pResult, void *pUserData) |
99 | { |
100 | CSpectator *pSelf = (CSpectator *)pUserData; |
101 | if(!pSelf->CanChangeSpectator()) |
102 | return; |
103 | |
104 | pSelf->SpectateNext(Reverse: false); |
105 | } |
106 | |
107 | void CSpectator::ConSpectatePrevious(IConsole::IResult *pResult, void *pUserData) |
108 | { |
109 | CSpectator *pSelf = (CSpectator *)pUserData; |
110 | if(!pSelf->CanChangeSpectator()) |
111 | return; |
112 | |
113 | pSelf->SpectateNext(Reverse: true); |
114 | } |
115 | |
116 | void CSpectator::ConSpectateClosest(IConsole::IResult *pResult, void *pUserData) |
117 | { |
118 | CSpectator *pSelf = (CSpectator *)pUserData; |
119 | if(!pSelf->CanChangeSpectator()) |
120 | return; |
121 | |
122 | const CGameClient::CSnapState &Snap = pSelf->m_pClient->m_Snap; |
123 | int SpectatorId = Snap.m_SpecInfo.m_SpectatorId; |
124 | |
125 | int NewSpectatorId = -1; |
126 | |
127 | vec2 CurPosition(pSelf->m_pClient->m_Camera.m_Center); |
128 | if(SpectatorId != SPEC_FREEVIEW) |
129 | { |
130 | const CNetObj_Character &CurCharacter = Snap.m_aCharacters[SpectatorId].m_Cur; |
131 | CurPosition.x = CurCharacter.m_X; |
132 | CurPosition.y = CurCharacter.m_Y; |
133 | } |
134 | |
135 | int ClosestDistance = std::numeric_limits<int>::max(); |
136 | for(int i = 0; i < MAX_CLIENTS; i++) |
137 | { |
138 | if(i == SpectatorId || !Snap.m_apPlayerInfos[i] || Snap.m_apPlayerInfos[i]->m_Team == TEAM_SPECTATORS || (SpectatorId == SPEC_FREEVIEW && i == Snap.m_LocalClientId)) |
139 | continue; |
140 | const CNetObj_Character &MaybeClosestCharacter = Snap.m_aCharacters[i].m_Cur; |
141 | int Distance = distance(a: CurPosition, b: vec2(MaybeClosestCharacter.m_X, MaybeClosestCharacter.m_Y)); |
142 | if(NewSpectatorId == -1 || Distance < ClosestDistance) |
143 | { |
144 | NewSpectatorId = i; |
145 | ClosestDistance = Distance; |
146 | } |
147 | } |
148 | if(NewSpectatorId > -1) |
149 | pSelf->Spectate(SpectatorId: NewSpectatorId); |
150 | } |
151 | |
152 | void CSpectator::ConMultiView(IConsole::IResult *pResult, void *pUserData) |
153 | { |
154 | CSpectator *pSelf = (CSpectator *)pUserData; |
155 | int Input = pResult->GetInteger(Index: 0); |
156 | |
157 | if(Input == -1) |
158 | std::fill(first: std::begin(arr&: pSelf->GameClient()->m_aMultiViewId), last: std::end(arr&: pSelf->GameClient()->m_aMultiViewId), value: false); // remove everyone from multiview |
159 | else if(Input < MAX_CLIENTS && Input >= 0) |
160 | pSelf->GameClient()->m_aMultiViewId[Input] = !pSelf->GameClient()->m_aMultiViewId[Input]; // activate or deactivate one player from multiview |
161 | } |
162 | |
163 | CSpectator::CSpectator() |
164 | { |
165 | m_SelectorMouse = vec2(0.0f, 0.0f); |
166 | OnReset(); |
167 | m_OldMouseX = m_OldMouseY = 0.0f; |
168 | } |
169 | |
170 | void CSpectator::OnConsoleInit() |
171 | { |
172 | Console()->Register(pName: "+spectate" , pParams: "" , Flags: CFGFLAG_CLIENT, pfnFunc: ConKeySpectator, pUser: this, pHelp: "Open spectator mode selector" ); |
173 | Console()->Register(pName: "spectate" , pParams: "i[spectator-id]" , Flags: CFGFLAG_CLIENT, pfnFunc: ConSpectate, pUser: this, pHelp: "Switch spectator mode" ); |
174 | Console()->Register(pName: "spectate_next" , pParams: "" , Flags: CFGFLAG_CLIENT, pfnFunc: ConSpectateNext, pUser: this, pHelp: "Spectate the next player" ); |
175 | Console()->Register(pName: "spectate_previous" , pParams: "" , Flags: CFGFLAG_CLIENT, pfnFunc: ConSpectatePrevious, pUser: this, pHelp: "Spectate the previous player" ); |
176 | Console()->Register(pName: "spectate_closest" , pParams: "" , Flags: CFGFLAG_CLIENT, pfnFunc: ConSpectateClosest, pUser: this, pHelp: "Spectate the closest player" ); |
177 | Console()->Register(pName: "spectate_multiview" , pParams: "i[id]" , Flags: CFGFLAG_CLIENT, pfnFunc: ConMultiView, pUser: this, pHelp: "Add/remove Client-IDs to spectate them exclusively (-1 to reset)" ); |
178 | } |
179 | |
180 | bool CSpectator::OnCursorMove(float x, float y, IInput::ECursorType CursorType) |
181 | { |
182 | if(!m_Active) |
183 | return false; |
184 | |
185 | Ui()->ConvertMouseMove(pX: &x, pY: &y, CursorType); |
186 | m_SelectorMouse += vec2(x, y); |
187 | return true; |
188 | } |
189 | |
190 | void CSpectator::OnRelease() |
191 | { |
192 | OnReset(); |
193 | } |
194 | |
195 | void CSpectator::OnRender() |
196 | { |
197 | if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
198 | return; |
199 | |
200 | if(!GameClient()->m_MultiViewActivated && m_MultiViewActivateDelay != 0.0f) |
201 | { |
202 | if(m_MultiViewActivateDelay <= Client()->LocalTime()) |
203 | { |
204 | m_MultiViewActivateDelay = 0.0f; |
205 | GameClient()->m_MultiViewActivated = true; |
206 | } |
207 | } |
208 | |
209 | if(!m_Active) |
210 | { |
211 | // closing the spectator menu |
212 | if(m_WasActive) |
213 | { |
214 | if(m_SelectedSpectatorId != NO_SELECTION) |
215 | { |
216 | if(m_SelectedSpectatorId == MULTI_VIEW) |
217 | GameClient()->m_MultiViewActivated = true; |
218 | else if(m_SelectedSpectatorId == SPEC_FREEVIEW || m_SelectedSpectatorId == SPEC_FOLLOW) |
219 | GameClient()->m_MultiViewActivated = false; |
220 | |
221 | if(!GameClient()->m_MultiViewActivated) |
222 | Spectate(SpectatorId: m_SelectedSpectatorId); |
223 | |
224 | if(GameClient()->m_MultiViewActivated && m_SelectedSpectatorId != MULTI_VIEW && m_pClient->m_Teams.Team(ClientId: m_SelectedSpectatorId) != GameClient()->m_MultiViewTeam) |
225 | { |
226 | GameClient()->ResetMultiView(); |
227 | Spectate(SpectatorId: m_SelectedSpectatorId); |
228 | m_MultiViewActivateDelay = Client()->LocalTime() + 0.3f; |
229 | } |
230 | } |
231 | m_WasActive = false; |
232 | } |
233 | return; |
234 | } |
235 | |
236 | if(m_SelectedSpectatorId != NO_SELECTION) |
237 | { |
238 | // clicking a component |
239 | if(m_Clicked) |
240 | { |
241 | if(!GameClient()->m_MultiViewActivated) |
242 | Spectate(SpectatorId: m_SelectedSpectatorId); |
243 | |
244 | if(m_SelectedSpectatorId == MULTI_VIEW) |
245 | GameClient()->m_MultiViewActivated = true; |
246 | else if(m_SelectedSpectatorId == SPEC_FREEVIEW || m_SelectedSpectatorId == SPEC_FOLLOW) |
247 | GameClient()->m_MultiViewActivated = false; |
248 | |
249 | if(!GameClient()->m_MultiViewActivated && m_SelectedSpectatorId >= 0 && m_SelectedSpectatorId < MAX_CLIENTS) |
250 | m_Clicked = false; |
251 | } |
252 | } |
253 | |
254 | if(!m_pClient->m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
255 | { |
256 | m_Active = false; |
257 | m_WasActive = false; |
258 | return; |
259 | } |
260 | |
261 | m_WasActive = true; |
262 | m_SelectedSpectatorId = NO_SELECTION; |
263 | |
264 | // draw background |
265 | float Width = 400 * 3.0f * Graphics()->ScreenAspect(); |
266 | float Height = 400 * 3.0f; |
267 | float ObjWidth = 300.0f; |
268 | float FontSize = 20.0f; |
269 | float BigFontSize = 20.0f; |
270 | float StartY = -190.0f; |
271 | float LineHeight = 60.0f; |
272 | float TeeSizeMod = 1.0f; |
273 | float RoundRadius = 30.0f; |
274 | bool Selected = false; |
275 | bool MultiViewSelected = false; |
276 | int TotalPlayers = 0; |
277 | int PerLine = 8; |
278 | float BoxMove = -10.0f; |
279 | float BoxOffset = 0.0f; |
280 | |
281 | for(const auto &pInfo : m_pClient->m_Snap.m_apInfoByDDTeamName) |
282 | { |
283 | if(!pInfo || pInfo->m_Team == TEAM_SPECTATORS) |
284 | continue; |
285 | |
286 | ++TotalPlayers; |
287 | } |
288 | |
289 | if(TotalPlayers > 32) |
290 | { |
291 | FontSize = 18.0f; |
292 | LineHeight = 30.0f; |
293 | TeeSizeMod = 0.7f; |
294 | PerLine = 16; |
295 | RoundRadius = 10.0f; |
296 | BoxMove = 3.0f; |
297 | BoxOffset = 6.0f; |
298 | } |
299 | if(TotalPlayers > 16) |
300 | { |
301 | ObjWidth = 600.0f; |
302 | } |
303 | |
304 | Graphics()->MapScreen(TopLeftX: 0, TopLeftY: 0, BottomRightX: Width, BottomRightY: Height); |
305 | |
306 | Graphics()->DrawRect(x: Width / 2.0f - ObjWidth, y: Height / 2.0f - 300.0f, w: ObjWidth * 2, h: 600.0f, Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.3f), Corners: IGraphics::CORNER_ALL, Rounding: 20.0f); |
307 | |
308 | // clamp mouse position to selector area |
309 | m_SelectorMouse.x = clamp(val: m_SelectorMouse.x, lo: -(ObjWidth - 20.0f), hi: ObjWidth - 20.0f); |
310 | m_SelectorMouse.y = clamp(val: m_SelectorMouse.y, lo: -280.0f, hi: 280.0f); |
311 | |
312 | // draw selections |
313 | if((Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecId == SPEC_FREEVIEW) || |
314 | (Client()->State() != IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_SpecInfo.m_SpectatorId == SPEC_FREEVIEW)) |
315 | { |
316 | Graphics()->DrawRect(x: Width / 2.0f - (ObjWidth - 20.0f), y: Height / 2.0f - 280.0f, w: ((ObjWidth * 2.0f) / 3.0f) - 40.0f, h: 60.0f, Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 20.0f); |
317 | } |
318 | |
319 | if(GameClient()->m_MultiViewActivated) |
320 | { |
321 | Graphics()->DrawRect(x: Width / 2.0f - (ObjWidth - 20.0f) + (ObjWidth * 2.0f / 3.0f), y: Height / 2.0f - 280.0f, w: ((ObjWidth * 2.0f) / 3.0f) - 40.0f, h: 60.0f, Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 20.0f); |
322 | } |
323 | |
324 | if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_LocalClientId >= 0 && m_pClient->m_DemoSpecId == SPEC_FOLLOW) |
325 | { |
326 | Graphics()->DrawRect(x: Width / 2.0f - (ObjWidth - 20.0f) + (ObjWidth * 2.0f * 2.0f / 3.0f), y: Height / 2.0f - 280.0f, w: ((ObjWidth * 2.0f) / 3.0f) - 40.0f, h: 60.0f, Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 20.0f); |
327 | } |
328 | |
329 | if(m_SelectorMouse.x >= -(ObjWidth - 20.0f) && m_SelectorMouse.x <= -(ObjWidth - 20.0f) + ((ObjWidth * 2.0f) / 3.0f) - 40.0f && |
330 | m_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f) |
331 | { |
332 | m_SelectedSpectatorId = SPEC_FREEVIEW; |
333 | Selected = true; |
334 | } |
335 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Selected ? 1.0f : 0.5f); |
336 | TextRender()->Text(x: Width / 2.0f - (ObjWidth - 40.0f), y: Height / 2.0f - 280.f + (60.f - BigFontSize) / 2.f, Size: BigFontSize, pText: Localize(pStr: "Free-View" ), LineWidth: -1.0f); |
337 | |
338 | if(m_SelectorMouse.x >= -(ObjWidth - 20.0f) + (ObjWidth * 2.0f / 3.0f) && m_SelectorMouse.x <= -(ObjWidth - 20.0f) + (ObjWidth * 2.0f / 3.0f) + ((ObjWidth * 2.0f) / 3.0f) - 40.0f && |
339 | m_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f) |
340 | { |
341 | m_SelectedSpectatorId = MULTI_VIEW; |
342 | MultiViewSelected = true; |
343 | } |
344 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: MultiViewSelected ? 1.0f : 0.5f); |
345 | TextRender()->Text(x: Width / 2.0f - (ObjWidth - 40.0f) + (ObjWidth * 2.0f / 3.0f), y: Height / 2.0f - 280.f + (60.f - BigFontSize) / 2.f, Size: BigFontSize, pText: Localize(pStr: "Multi-View" ), LineWidth: -1.0f); |
346 | |
347 | if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_LocalClientId >= 0) |
348 | { |
349 | Selected = false; |
350 | if(m_SelectorMouse.x >= -(ObjWidth - 20.0f) + (ObjWidth * 2.0f * 2.0f / 3.0f) && m_SelectorMouse.x <= -(ObjWidth - 20.0f) + (ObjWidth * 2.0f * 2.0f / 3.0f) + ((ObjWidth * 2.0f) / 3.0f) - 40.0f && |
351 | m_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f) |
352 | { |
353 | m_SelectedSpectatorId = SPEC_FOLLOW; |
354 | Selected = true; |
355 | } |
356 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Selected ? 1.0f : 0.5f); |
357 | TextRender()->Text(x: Width / 2.0f - (ObjWidth - 40.0f) + (ObjWidth * 2.0f * 2.0f / 3.0f), y: Height / 2.0f - 280.0f + (60.f - BigFontSize) / 2.f, Size: BigFontSize, pText: Localize(pStr: "Follow" ), LineWidth: -1.0f); |
358 | } |
359 | |
360 | float x = -(ObjWidth - 35.0f), y = StartY; |
361 | |
362 | int OldDDTeam = -1; |
363 | |
364 | for(int i = 0, Count = 0; i < MAX_CLIENTS; ++i) |
365 | { |
366 | if(!m_pClient->m_Snap.m_apInfoByDDTeamName[i] || m_pClient->m_Snap.m_apInfoByDDTeamName[i]->m_Team == TEAM_SPECTATORS) |
367 | continue; |
368 | |
369 | ++Count; |
370 | |
371 | if(Count == PerLine + 1 || (Count > PerLine + 1 && (Count - 1) % PerLine == 0)) |
372 | { |
373 | x += 290.0f; |
374 | y = StartY; |
375 | } |
376 | |
377 | const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_apInfoByDDTeamName[i]; |
378 | int DDTeam = m_pClient->m_Teams.Team(ClientId: pInfo->m_ClientId); |
379 | int NextDDTeam = 0; |
380 | |
381 | for(int j = i + 1; j < MAX_CLIENTS; j++) |
382 | { |
383 | const CNetObj_PlayerInfo *pInfo2 = m_pClient->m_Snap.m_apInfoByDDTeamName[j]; |
384 | |
385 | if(!pInfo2 || pInfo2->m_Team == TEAM_SPECTATORS) |
386 | continue; |
387 | |
388 | NextDDTeam = m_pClient->m_Teams.Team(ClientId: pInfo2->m_ClientId); |
389 | break; |
390 | } |
391 | |
392 | if(OldDDTeam == -1) |
393 | { |
394 | for(int j = i - 1; j >= 0; j--) |
395 | { |
396 | const CNetObj_PlayerInfo *pInfo2 = m_pClient->m_Snap.m_apInfoByDDTeamName[j]; |
397 | |
398 | if(!pInfo2 || pInfo2->m_Team == TEAM_SPECTATORS) |
399 | continue; |
400 | |
401 | OldDDTeam = m_pClient->m_Teams.Team(ClientId: pInfo2->m_ClientId); |
402 | break; |
403 | } |
404 | } |
405 | |
406 | if(DDTeam != TEAM_FLOCK) |
407 | { |
408 | const ColorRGBA Color = m_pClient->GetDDTeamColor(DDTeam).WithAlpha(alpha: 0.5f); |
409 | int Corners = 0; |
410 | if(OldDDTeam != DDTeam) |
411 | Corners |= IGraphics::CORNER_TL | IGraphics::CORNER_TR; |
412 | if(NextDDTeam != DDTeam) |
413 | Corners |= IGraphics::CORNER_BL | IGraphics::CORNER_BR; |
414 | Graphics()->DrawRect(x: Width / 2.0f + x - 10.0f + BoxOffset, y: Height / 2.0f + y + BoxMove, w: 270.0f - BoxOffset, h: LineHeight, Color, Corners, Rounding: RoundRadius); |
415 | } |
416 | |
417 | OldDDTeam = DDTeam; |
418 | |
419 | if((Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecId == m_pClient->m_Snap.m_apInfoByDDTeamName[i]->m_ClientId) || (Client()->State() != IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_SpecInfo.m_SpectatorId == m_pClient->m_Snap.m_apInfoByDDTeamName[i]->m_ClientId)) |
420 | { |
421 | Graphics()->DrawRect(x: Width / 2.0f + x - 10.0f + BoxOffset, y: Height / 2.0f + y + BoxMove, w: 270.0f - BoxOffset, h: LineHeight, Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: RoundRadius); |
422 | } |
423 | |
424 | Selected = false; |
425 | if(m_SelectorMouse.x >= x - 10.0f && m_SelectorMouse.x < x + 260.0f && |
426 | m_SelectorMouse.y >= y - (LineHeight / 6.0f) && m_SelectorMouse.y < y + (LineHeight * 5.0f / 6.0f)) |
427 | { |
428 | m_SelectedSpectatorId = m_pClient->m_Snap.m_apInfoByDDTeamName[i]->m_ClientId; |
429 | Selected = true; |
430 | if(GameClient()->m_MultiViewActivated && m_Clicked) |
431 | { |
432 | if(GameClient()->m_MultiViewTeam == DDTeam) |
433 | { |
434 | GameClient()->m_aMultiViewId[m_SelectedSpectatorId] = !GameClient()->m_aMultiViewId[m_SelectedSpectatorId]; |
435 | if(!GameClient()->m_aMultiViewId[m_pClient->m_Snap.m_SpecInfo.m_SpectatorId]) |
436 | { |
437 | int NewClientId = GameClient()->FindFirstMultiViewId(); |
438 | if(NewClientId < MAX_CLIENTS && NewClientId >= 0) |
439 | { |
440 | GameClient()->CleanMultiViewId(ClientId: NewClientId); |
441 | GameClient()->m_aMultiViewId[NewClientId] = true; |
442 | Spectate(SpectatorId: NewClientId); |
443 | } |
444 | } |
445 | } |
446 | else |
447 | { |
448 | GameClient()->ResetMultiView(); |
449 | Spectate(SpectatorId: m_SelectedSpectatorId); |
450 | m_MultiViewActivateDelay = Client()->LocalTime() + 0.3f; |
451 | } |
452 | m_Clicked = false; |
453 | } |
454 | } |
455 | float TeeAlpha; |
456 | if(Client()->State() == IClient::STATE_DEMOPLAYBACK && |
457 | !m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_apInfoByDDTeamName[i]->m_ClientId].m_Active) |
458 | { |
459 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 0.25f); |
460 | TeeAlpha = 0.5f; |
461 | } |
462 | else |
463 | { |
464 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: Selected ? 1.0f : 0.5f); |
465 | TeeAlpha = 1.0f; |
466 | } |
467 | TextRender()->Text(x: Width / 2.0f + x + 50.0f, y: Height / 2.0f + y + BoxMove + (LineHeight - FontSize) / 2.f, Size: FontSize, pText: m_pClient->m_aClients[m_pClient->m_Snap.m_apInfoByDDTeamName[i]->m_ClientId].m_aName, LineWidth: 220.0f); |
468 | |
469 | if(GameClient()->m_MultiViewActivated) |
470 | { |
471 | if(GameClient()->m_aMultiViewId[m_pClient->m_Snap.m_apInfoByDDTeamName[i]->m_ClientId]) |
472 | { |
473 | TextRender()->TextColor(r: 0.1f, g: 1.0f, b: 0.1f, a: Selected ? 1.0f : 0.5f); |
474 | TextRender()->Text(x: Width / 2.0f + x + 50.0f + 180.0f, y: Height / 2.0f + y + BoxMove + (LineHeight - FontSize) / 2.f, Size: FontSize - 3, pText: "⬤" , LineWidth: 220.0f); |
475 | } |
476 | else if(GameClient()->m_MultiViewTeam == DDTeam) |
477 | { |
478 | TextRender()->TextColor(r: 1.0f, g: 0.1f, b: 0.1f, a: Selected ? 1.0f : 0.5f); |
479 | TextRender()->Text(x: Width / 2.0f + x + 50.0f + 180.0f, y: Height / 2.0f + y + BoxMove + (LineHeight - FontSize) / 2.f, Size: FontSize - 3, pText: "◯" , LineWidth: 220.0f); |
480 | } |
481 | } |
482 | |
483 | // flag |
484 | if(m_pClient->m_Snap.m_pGameInfoObj && (m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS) && |
485 | m_pClient->m_Snap.m_pGameDataObj && (m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierRed == m_pClient->m_Snap.m_apInfoByDDTeamName[i]->m_ClientId || m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue == m_pClient->m_Snap.m_apInfoByDDTeamName[i]->m_ClientId)) |
486 | { |
487 | Graphics()->BlendNormal(); |
488 | if(m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue == m_pClient->m_Snap.m_apInfoByDDTeamName[i]->m_ClientId) |
489 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteFlagBlue); |
490 | else |
491 | Graphics()->TextureSet(Texture: GameClient()->m_GameSkin.m_SpriteFlagRed); |
492 | |
493 | Graphics()->QuadsBegin(); |
494 | Graphics()->QuadsSetSubset(TopLeftU: 1, TopLeftV: 0, BottomRightU: 0, BottomRightV: 1); |
495 | |
496 | float Size = LineHeight; |
497 | IGraphics::CQuadItem QuadItem(Width / 2.0f + x - LineHeight / 5.0f, Height / 2.0f + y - LineHeight / 3.0f, Size / 2.0f, Size); |
498 | Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1); |
499 | Graphics()->QuadsEnd(); |
500 | } |
501 | |
502 | CTeeRenderInfo TeeInfo = m_pClient->m_aClients[m_pClient->m_Snap.m_apInfoByDDTeamName[i]->m_ClientId].m_RenderInfo; |
503 | TeeInfo.m_Size *= TeeSizeMod; |
504 | |
505 | const CAnimState *pIdleState = CAnimState::GetIdle(); |
506 | vec2 OffsetToMid; |
507 | CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: pIdleState, pInfo: &TeeInfo, TeeOffsetToMid&: OffsetToMid); |
508 | vec2 TeeRenderPos(Width / 2.0f + x + 20.0f, Height / 2.0f + y + BoxMove + LineHeight / 2.0f + OffsetToMid.y); |
509 | |
510 | RenderTools()->RenderTee(pAnim: pIdleState, pInfo: &TeeInfo, Emote: EMOTE_NORMAL, Dir: vec2(1.0f, 0.0f), Pos: TeeRenderPos, Alpha: TeeAlpha); |
511 | |
512 | if(m_pClient->m_aClients[m_pClient->m_Snap.m_apInfoByDDTeamName[i]->m_ClientId].m_Friend) |
513 | { |
514 | ColorRGBA rgb = color_cast<ColorRGBA>(hsl: ColorHSLA(g_Config.m_ClMessageFriendColor)); |
515 | TextRender()->TextColor(rgb: rgb.WithAlpha(alpha: 1.f)); |
516 | TextRender()->Text(x: Width / 2.0f + x - TeeInfo.m_Size / 2.0f, y: Height / 2.0f + y + BoxMove + (LineHeight - FontSize) / 2.f, Size: FontSize, pText: "♥" , LineWidth: 220.0f); |
517 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
518 | } |
519 | |
520 | y += LineHeight; |
521 | } |
522 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
523 | |
524 | RenderTools()->RenderCursor(Center: m_SelectorMouse + vec2(Width, Height) / 2, Size: 48.0f); |
525 | } |
526 | |
527 | void CSpectator::OnReset() |
528 | { |
529 | m_WasActive = false; |
530 | m_Active = false; |
531 | m_SelectedSpectatorId = NO_SELECTION; |
532 | } |
533 | |
534 | void CSpectator::Spectate(int SpectatorId) |
535 | { |
536 | if(Client()->State() == IClient::STATE_DEMOPLAYBACK) |
537 | { |
538 | m_pClient->m_DemoSpecId = clamp(val: SpectatorId, lo: (int)SPEC_FOLLOW, hi: MAX_CLIENTS - 1); |
539 | // The tick must be rendered for the spectator mode to be updated, so we do it manually when demo playback is paused |
540 | if(DemoPlayer()->BaseInfo()->m_Paused) |
541 | GameClient()->m_Menus.DemoSeekTick(TickOffset: IDemoPlayer::TICK_CURRENT); |
542 | return; |
543 | } |
544 | |
545 | if(m_pClient->m_Snap.m_SpecInfo.m_SpectatorId == SpectatorId) |
546 | return; |
547 | |
548 | CNetMsg_Cl_SetSpectatorMode Msg; |
549 | Msg.m_SpectatorId = SpectatorId; |
550 | Client()->SendPackMsgActive(pMsg: &Msg, Flags: MSGFLAG_VITAL); |
551 | } |
552 | |
553 | void CSpectator::SpectateClosest() |
554 | { |
555 | ConSpectateClosest(NULL, pUserData: this); |
556 | } |
557 | |
558 | bool CSpectator::OnInput(const IInput::CEvent &Event) |
559 | { |
560 | if(m_Active && Event.m_Flags & IInput::FLAG_PRESS && Event.m_Key == KEY_MOUSE_1) |
561 | { |
562 | m_Clicked = true; |
563 | return true; |
564 | } |
565 | else if(Event.m_Flags & IInput::FLAG_RELEASE && Event.m_Key == KEY_MOUSE_1) |
566 | { |
567 | m_Clicked = false; |
568 | return false; |
569 | } |
570 | return false; |
571 | } |
572 | |