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 "camera.h"
5
6#include "controls.h"
7
8#include <base/log.h>
9#include <base/vmath.h>
10
11#include <engine/shared/config.h>
12
13#include <game/client/gameclient.h>
14#include <game/collision.h>
15#include <game/localization.h>
16#include <game/mapitems.h>
17
18#include <limits>
19
20CCamera::CCamera()
21{
22 m_CamType = CAMTYPE_UNDEFINED;
23 m_ZoomSet = false;
24 m_Zoom = 1.0f;
25 m_Zooming = false;
26 m_ForceFreeview = false;
27 m_GotoSwitchOffset = 0;
28 m_GotoTeleOffset = 0;
29 m_GotoSwitchLastPos = ivec2(-1, -1);
30 m_GotoTeleLastPos = ivec2(-1, -1);
31
32 std::fill(first: std::begin(arr&: m_aLastPos), last: std::end(arr&: m_aLastPos), value: vec2(0.0f, 0.0f));
33 m_PrevCenter = vec2(0, 0);
34 m_Center = vec2(0, 0);
35
36 m_PrevSpecId = -1;
37 m_WasSpectating = false;
38
39 m_CameraSmoothing = false;
40
41 m_LastTargetPos = vec2(0, 0);
42 m_DyncamTargetCameraOffset = vec2(0, 0);
43 std::fill(first: std::begin(arr&: m_aDyncamCurrentCameraOffset), last: std::end(arr&: m_aDyncamCurrentCameraOffset), value: vec2(0.0f, 0.0f));
44 m_DyncamSmoothingSpeedBias = 0.5f;
45
46 m_AutoSpecCamera = true;
47 m_AutoSpecCameraZooming = false;
48 m_CanUseCameraInfo = false;
49 m_UsingAutoSpecCamera = false;
50
51 m_aAutoSpecCameraTooltip[0] = '\0';
52}
53
54float CCamera::CameraSmoothingProgress(float CurrentTime) const
55{
56 float Progress = (CurrentTime - m_CameraSmoothingStart) / (m_CameraSmoothingEnd - m_CameraSmoothingStart);
57 return 1.0 - std::pow(x: 2.0, y: -10.0 * Progress);
58}
59
60float CCamera::ZoomProgress(float CurrentTime) const
61{
62 return (CurrentTime - m_ZoomSmoothingStart) / (m_ZoomSmoothingEnd - m_ZoomSmoothingStart);
63}
64
65void CCamera::ScaleZoom(float Factor)
66{
67 float CurrentTarget = m_Zooming ? m_ZoomSmoothingTarget : m_Zoom;
68 ChangeZoom(Target: CurrentTarget * Factor, Smoothness: GameClient()->m_Snap.m_SpecInfo.m_Active && GameClient()->m_MultiViewActivated ? g_Config.m_ClMultiViewZoomSmoothness : g_Config.m_ClSmoothZoomTime, IsUser: true);
69
70 m_AutoSpecCamera = false;
71}
72
73float CCamera::MaxZoomLevel()
74{
75 return (g_Config.m_ClLimitMaxZoomLevel) ? ((Graphics()->IsTileBufferingEnabled() ? 240 : 30)) : std::numeric_limits<float>::max();
76}
77
78float CCamera::MinZoomLevel()
79{
80 return 0.01f;
81}
82
83void CCamera::ChangeZoom(float Target, int Smoothness, bool IsUser)
84{
85 if(Target > MaxZoomLevel() || Target < MinZoomLevel())
86 {
87 return;
88 }
89
90 float Now = Client()->LocalTime();
91 float Current = m_Zoom;
92 float Derivative = 0.0f;
93 if(m_Zooming)
94 {
95 float Progress = ZoomProgress(CurrentTime: Now);
96 Current = m_ZoomSmoothing.Evaluate(t: Progress);
97 Derivative = m_ZoomSmoothing.Derivative(t: Progress);
98 }
99
100 m_ZoomSmoothingTarget = Target;
101 m_ZoomSmoothing = CCubicBezier::With(Start: Current, StartDerivative: Derivative, EndDerivative: 0, End: m_ZoomSmoothingTarget);
102 m_ZoomSmoothingStart = Now;
103 m_ZoomSmoothingEnd = Now + (float)Smoothness / 1000;
104
105 if(IsUser)
106 m_UserZoomTarget = Target;
107
108 m_Zooming = true;
109}
110
111void CCamera::ResetAutoSpecCamera()
112{
113 m_AutoSpecCamera = true;
114}
115
116void CCamera::UpdateCamera()
117{
118 // use hardcoded smooth camera for spectating unless player explicitly turn it off
119 bool CanUseCameraInfo = !GameClient()->m_MultiViewActivated;
120 if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
121 {
122 // only follow mode have the correct camera info
123 CanUseCameraInfo = CanUseCameraInfo && GameClient()->m_DemoSpecId == SPEC_FOLLOW;
124 }
125 else
126 {
127 CanUseCameraInfo = CanUseCameraInfo && GameClient()->m_Snap.m_SpecInfo.m_Active &&
128 GameClient()->m_Snap.m_SpecInfo.m_SpectatorId >= 0 &&
129 GameClient()->m_Snap.m_SpecInfo.m_SpectatorId != GameClient()->m_aLocalIds[0] &&
130 (!GameClient()->Client()->DummyConnected() || GameClient()->m_Snap.m_SpecInfo.m_SpectatorId != GameClient()->m_aLocalIds[1]);
131 }
132
133 bool UsingAutoSpecCamera = m_AutoSpecCamera && CanUseAutoSpecCamera();
134 float CurrentZoom = m_Zooming ? m_ZoomSmoothingTarget : m_Zoom;
135 bool ZoomChanged = false;
136 if(CanUseCameraInfo && UsingAutoSpecCamera && CurrentZoom != GameClient()->m_Snap.m_SpecInfo.m_Zoom)
137 {
138 // start spectating player / turn on auto spec camera
139 bool ChangeTarget = m_PrevSpecId != GameClient()->m_Snap.m_SpecInfo.m_SpectatorId;
140 float SmoothTime = ChangeTarget ? g_Config.m_ClSmoothSpectatingTime : 250;
141 ChangeZoom(Target: GameClient()->m_Snap.m_SpecInfo.m_Zoom, Smoothness: SmoothTime, IsUser: false);
142
143 // it is auto spec camera zooming if only the zoom is changed during activation, not at the start of the activation
144 m_AutoSpecCameraZooming = !ChangeTarget && CanUseCameraInfo && m_UsingAutoSpecCamera;
145
146 ZoomChanged = true;
147 }
148 else if((CanUseCameraInfo && !UsingAutoSpecCamera) && CurrentZoom != m_UserZoomTarget)
149 {
150 // turning off auto spec camera
151 ChangeZoom(Target: m_UserZoomTarget, Smoothness: g_Config.m_ClSmoothZoomTime, IsUser: false);
152 m_AutoSpecCameraZooming = false;
153
154 ZoomChanged = true;
155 }
156 else if(!CanUseCameraInfo && CurrentZoom != m_UserZoomTarget)
157 {
158 // stop spectating player
159 if(!GameClient()->m_MultiViewActivated)
160 ChangeZoom(Target: m_UserZoomTarget, Smoothness: g_Config.m_ClSmoothZoomTime, IsUser: false);
161 m_AutoSpecCameraZooming = false;
162
163 ZoomChanged = true;
164 }
165
166 // snap zoom when going in and out of spectating
167 if(ZoomChanged && m_WasSpectating != GameClient()->m_Snap.m_SpecInfo.m_Active)
168 {
169 m_Zoom = m_ZoomSmoothingTarget;
170 m_Zooming = false;
171 }
172
173 if(m_Zooming)
174 {
175 float Time = Client()->LocalTime();
176 if(Time >= m_ZoomSmoothingEnd)
177 {
178 m_Zoom = m_ZoomSmoothingTarget;
179 m_Zooming = false;
180 m_AutoSpecCameraZooming = false;
181 }
182 else
183 {
184 const float OldLevel = m_Zoom;
185 m_Zoom = m_ZoomSmoothing.Evaluate(t: ZoomProgress(CurrentTime: Time));
186 if((OldLevel < m_ZoomSmoothingTarget && m_Zoom > m_ZoomSmoothingTarget) || (OldLevel > m_ZoomSmoothingTarget && m_Zoom < m_ZoomSmoothingTarget))
187 {
188 m_Zoom = m_ZoomSmoothingTarget;
189 m_Zooming = false;
190 m_AutoSpecCameraZooming = false;
191 }
192 }
193 m_Zoom = std::clamp(val: m_Zoom, lo: MinZoomLevel(), hi: MaxZoomLevel());
194 }
195
196 if(!ZoomAllowed())
197 {
198 m_ZoomSet = false;
199 m_Zoom = 1.0f;
200 m_Zooming = false;
201 m_AutoSpecCameraZooming = false;
202 }
203 else if(!m_ZoomSet && g_Config.m_ClDefaultZoom != 10)
204 {
205 m_ZoomSet = true;
206 OnReset();
207 }
208
209 if(GameClient()->m_Snap.m_SpecInfo.m_Active && !GameClient()->m_Snap.m_SpecInfo.m_UsePosition)
210 {
211 m_aDyncamCurrentCameraOffset[g_Config.m_ClDummy] = vec2(0, 0);
212 m_CanUseCameraInfo = CanUseCameraInfo;
213 m_UsingAutoSpecCamera = UsingAutoSpecCamera;
214 return;
215 }
216
217 vec2 TargetPos = CanUseCameraInfo ? GameClient()->m_CursorInfo.Target() : GameClient()->m_Controls.m_aMousePos[g_Config.m_ClDummy];
218 int Smoothness = CanUseCameraInfo ? 50 : g_Config.m_ClDyncamSmoothness;
219 int Stabilizing = CanUseCameraInfo ? 50 : g_Config.m_ClDyncamStabilizing;
220 bool IsDyncam = CanUseCameraInfo ? true : g_Config.m_ClDyncam;
221
222 float DeltaTime = Client()->RenderFrameTime();
223
224 if(Smoothness > 0)
225 {
226 float CameraSpeed = (1.0f - (Smoothness / 100.0f)) * 9.5f + 0.5f;
227 float CameraStabilizingFactor = 1 + Stabilizing / 100.0f;
228
229 m_DyncamSmoothingSpeedBias += CameraSpeed * DeltaTime;
230 if(IsDyncam)
231 {
232 m_DyncamSmoothingSpeedBias -= length(a: TargetPos - m_LastTargetPos) * std::log10(x: CameraStabilizingFactor) * 0.02f;
233 m_DyncamSmoothingSpeedBias = std::clamp(val: m_DyncamSmoothingSpeedBias, lo: 0.5f, hi: CameraSpeed);
234 }
235 else
236 {
237 m_DyncamSmoothingSpeedBias = std::max(a: 5.0f, b: CameraSpeed); // make sure toggle back is fast
238 }
239 }
240
241 m_DyncamTargetCameraOffset = vec2(0, 0);
242 float l = length(a: TargetPos);
243 if(l > 0.0001f) // make sure that this isn't 0
244 {
245 float CurrentDeadzone = Deadzone();
246 float CurrentFollowFactor = FollowFactor();
247
248 // use provided camera setting from server
249 if(CanUseCameraInfo)
250 {
251 CurrentDeadzone = GameClient()->m_Snap.m_SpecInfo.m_Deadzone;
252 CurrentFollowFactor = GameClient()->m_Snap.m_SpecInfo.m_FollowFactor;
253
254 if(!UsingAutoSpecCamera)
255 {
256 // turn off dyncam if user zooms when spectating
257 CurrentDeadzone = 0;
258 CurrentFollowFactor = 0;
259 }
260 }
261
262 float OffsetAmount = std::max(a: l - CurrentDeadzone, b: 0.0f) * (CurrentFollowFactor / 100.0f);
263
264 if(CanUseCameraInfo)
265 {
266 OffsetAmount = std::min(a: OffsetAmount, b: 350.0f * m_Zoom);
267 }
268
269 m_DyncamTargetCameraOffset = normalize_pre_length(v: TargetPos, len: l) * OffsetAmount;
270 }
271
272 m_LastTargetPos = TargetPos;
273 vec2 CurrentCameraOffset = m_aDyncamCurrentCameraOffset[g_Config.m_ClDummy];
274 float SpeedBias = m_CameraSmoothing ? 50.0f : m_DyncamSmoothingSpeedBias;
275 if(Smoothness > 0)
276 CurrentCameraOffset += (m_DyncamTargetCameraOffset - CurrentCameraOffset) * std::min(a: DeltaTime * SpeedBias, b: 1.0f);
277 else
278 CurrentCameraOffset = m_DyncamTargetCameraOffset;
279
280 // directly put the camera in place when switching in and out of freeview or spectate mode
281 if(m_CanUseCameraInfo != CanUseCameraInfo)
282 {
283 CurrentCameraOffset = m_DyncamTargetCameraOffset;
284 }
285
286 m_aDyncamCurrentCameraOffset[g_Config.m_ClDummy] = CurrentCameraOffset;
287 m_CanUseCameraInfo = CanUseCameraInfo;
288 m_UsingAutoSpecCamera = UsingAutoSpecCamera;
289}
290
291void CCamera::OnRender()
292{
293 if(m_CameraSmoothing)
294 {
295 if(!GameClient()->m_Snap.m_SpecInfo.m_Active)
296 {
297 m_Center = m_CameraSmoothingTarget;
298 m_CameraSmoothing = false;
299 }
300 else
301 {
302 float Time = Client()->LocalTime();
303 if(Time >= m_CameraSmoothingEnd)
304 {
305 m_Center = m_CameraSmoothingTarget;
306 m_CameraSmoothing = false;
307 }
308 else
309 {
310 m_CameraSmoothingCenter = vec2(m_CameraSmoothingBezierX.Evaluate(t: CameraSmoothingProgress(CurrentTime: Time)), m_CameraSmoothingBezierY.Evaluate(t: CameraSmoothingProgress(CurrentTime: Time)));
311 if(distance(a: m_CameraSmoothingCenter, b: m_CameraSmoothingTarget) <= 0.1f)
312 {
313 m_Center = m_CameraSmoothingTarget;
314 m_CameraSmoothing = false;
315 }
316 }
317 }
318 }
319
320 // update camera center
321 if(GameClient()->m_Snap.m_SpecInfo.m_Active && !GameClient()->m_Snap.m_SpecInfo.m_UsePosition)
322 {
323 if(m_CamType != CAMTYPE_SPEC)
324 {
325 m_aLastPos[g_Config.m_ClDummy] = GameClient()->m_Controls.m_aMousePos[g_Config.m_ClDummy];
326 GameClient()->m_Controls.m_aMousePos[g_Config.m_ClDummy] = m_PrevCenter;
327 GameClient()->m_Controls.m_aMouseInputType[g_Config.m_ClDummy] = CControls::EMouseInputType::AUTOMATED;
328 GameClient()->m_Controls.ClampMousePos();
329 m_CamType = CAMTYPE_SPEC;
330 }
331 m_Center = GameClient()->m_Controls.m_aMousePos[g_Config.m_ClDummy];
332 }
333 else
334 {
335 if(m_CamType != CAMTYPE_PLAYER)
336 {
337 GameClient()->m_Controls.m_aMousePos[g_Config.m_ClDummy] = m_aLastPos[g_Config.m_ClDummy];
338 GameClient()->m_Controls.m_aMouseInputType[g_Config.m_ClDummy] = CControls::EMouseInputType::AUTOMATED;
339 GameClient()->m_Controls.ClampMousePos();
340 m_CamType = CAMTYPE_PLAYER;
341 }
342
343 if(GameClient()->m_Snap.m_SpecInfo.m_Active)
344 m_Center = GameClient()->m_Snap.m_SpecInfo.m_Position + m_aDyncamCurrentCameraOffset[g_Config.m_ClDummy];
345 else
346 m_Center = GameClient()->m_LocalCharacterPos + m_aDyncamCurrentCameraOffset[g_Config.m_ClDummy];
347 }
348
349 if(m_ForceFreeview && m_CamType == CAMTYPE_SPEC)
350 {
351 GameClient()->m_Controls.m_aMouseInputType[g_Config.m_ClDummy] = CControls::EMouseInputType::AUTOMATED;
352 m_Center = GameClient()->m_Controls.m_aMousePos[g_Config.m_ClDummy] = m_ForceFreeviewPos;
353 m_ForceFreeview = false;
354 }
355 else
356 {
357 m_ForceFreeviewPos = m_Center;
358 }
359
360 const int SpecId = GameClient()->m_Snap.m_SpecInfo.m_SpectatorId;
361
362 // start smoothing from the current position when the target changes
363 if(m_CameraSmoothing && SpecId != m_PrevSpecId)
364 m_CameraSmoothing = false;
365
366 if(GameClient()->m_Snap.m_SpecInfo.m_Active &&
367 (SpecId != m_PrevSpecId ||
368 (m_CameraSmoothing && m_CameraSmoothingTarget != m_Center)) && // the target is moving during camera smoothing
369 !(!m_WasSpectating && m_Center != m_PrevCenter) && // dont smooth when starting to spectate
370 m_CamType != CAMTYPE_SPEC &&
371 !GameClient()->m_MultiViewActivated)
372 {
373 float Now = Client()->LocalTime();
374 if(!m_CameraSmoothing)
375 m_CenterBeforeSmoothing = m_PrevCenter;
376
377 vec2 Derivative = {0.f, 0.f};
378 if(m_CameraSmoothing)
379 {
380 float Progress = CameraSmoothingProgress(CurrentTime: Now);
381 Derivative.x = m_CameraSmoothingBezierX.Derivative(t: Progress);
382 Derivative.y = m_CameraSmoothingBezierY.Derivative(t: Progress);
383 }
384
385 m_CameraSmoothingTarget = m_Center;
386 m_CameraSmoothingBezierX = CCubicBezier::With(Start: m_CenterBeforeSmoothing.x, StartDerivative: Derivative.x, EndDerivative: 0, End: m_CameraSmoothingTarget.x);
387 m_CameraSmoothingBezierY = CCubicBezier::With(Start: m_CenterBeforeSmoothing.y, StartDerivative: Derivative.y, EndDerivative: 0, End: m_CameraSmoothingTarget.y);
388
389 if(!m_CameraSmoothing)
390 {
391 m_CameraSmoothingStart = Now;
392 m_CameraSmoothingEnd = Now + (float)g_Config.m_ClSmoothSpectatingTime / 1000.0f;
393 }
394
395 if(!m_CameraSmoothing)
396 m_CameraSmoothingCenter = m_PrevCenter;
397
398 m_CameraSmoothing = true;
399 }
400
401 if(m_CameraSmoothing)
402 m_Center = m_CameraSmoothingCenter;
403
404 m_PrevCenter = m_Center;
405 m_PrevSpecId = SpecId;
406
407 // demo always count as spectating
408 m_WasSpectating = GameClient()->m_Snap.m_SpecInfo.m_Active;
409}
410
411void CCamera::OnConsoleInit()
412{
413 Console()->Register(pName: "zoom+", pParams: "?f[amount]", Flags: CFGFLAG_CLIENT, pfnFunc: ConZoomPlus, pUser: this, pHelp: "Zoom increase");
414 Console()->Register(pName: "zoom-", pParams: "?f[amount]", Flags: CFGFLAG_CLIENT, pfnFunc: ConZoomMinus, pUser: this, pHelp: "Zoom decrease");
415 Console()->Register(pName: "zoom", pParams: "?f", Flags: CFGFLAG_CLIENT, pfnFunc: ConZoom, pUser: this, pHelp: "Change zoom");
416 Console()->Register(pName: "set_view", pParams: "i[x]i[y]", Flags: CFGFLAG_CLIENT, pfnFunc: ConSetView, pUser: this, pHelp: "Set camera position to x and y in the map");
417 Console()->Register(pName: "set_view_relative", pParams: "i[x]i[y]", Flags: CFGFLAG_CLIENT, pfnFunc: ConSetViewRelative, pUser: this, pHelp: "Set camera position relative to current view in the map");
418 Console()->Register(pName: "goto_switch", pParams: "i[number]?i[offset]", Flags: CFGFLAG_CLIENT, pfnFunc: ConGotoSwitch, pUser: this, pHelp: "View switch found (at offset) with given number");
419 Console()->Register(pName: "goto_tele", pParams: "i[number]?i[offset]", Flags: CFGFLAG_CLIENT, pfnFunc: ConGotoTele, pUser: this, pHelp: "View tele found (at offset) with given number");
420}
421
422void CCamera::OnReset()
423{
424 m_CameraSmoothing = false;
425
426 m_Zoom = CCamera::ZoomStepsToValue(Steps: g_Config.m_ClDefaultZoom - 10);
427 m_Zooming = false;
428 m_AutoSpecCameraZooming = false;
429 m_UserZoomTarget = CCamera::ZoomStepsToValue(Steps: g_Config.m_ClDefaultZoom - 10);
430}
431
432void CCamera::ConZoomPlus(IConsole::IResult *pResult, void *pUserData)
433{
434 CCamera *pSelf = (CCamera *)pUserData;
435 if(!pSelf->ZoomAllowed())
436 return;
437
438 float ZoomAmount = pResult->NumArguments() ? pResult->GetFloat(Index: 0) : 1.0f;
439
440 pSelf->ScaleZoom(Factor: CCamera::ZoomStepsToValue(Steps: ZoomAmount));
441
442 if(pSelf->GameClient()->m_MultiViewActivated)
443 pSelf->GameClient()->m_MultiViewPersonalZoom += ZoomAmount;
444}
445void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData)
446{
447 CCamera *pSelf = (CCamera *)pUserData;
448 if(!pSelf->ZoomAllowed())
449 return;
450
451 float ZoomAmount = pResult->NumArguments() ? pResult->GetFloat(Index: 0) : 1.0f;
452 ZoomAmount *= -1.0f;
453
454 pSelf->ScaleZoom(Factor: CCamera::ZoomStepsToValue(Steps: ZoomAmount));
455
456 if(pSelf->GameClient()->m_MultiViewActivated)
457 pSelf->GameClient()->m_MultiViewPersonalZoom += ZoomAmount;
458}
459void CCamera::ConZoom(IConsole::IResult *pResult, void *pUserData)
460{
461 CCamera *pSelf = (CCamera *)pUserData;
462 if(!pSelf->ZoomAllowed())
463 return;
464
465 bool IsReset = !pResult->NumArguments();
466
467 float TargetLevel = !IsReset ? pResult->GetFloat(Index: 0) : g_Config.m_ClDefaultZoom;
468
469 if(!pSelf->CanUseAutoSpecCamera() || !pSelf->m_CanUseCameraInfo)
470 pSelf->ChangeZoom(Target: CCamera::ZoomStepsToValue(Steps: TargetLevel - 10.0f), Smoothness: pSelf->GameClient()->m_Snap.m_SpecInfo.m_Active && pSelf->GameClient()->m_MultiViewActivated ? g_Config.m_ClMultiViewZoomSmoothness : g_Config.m_ClSmoothZoomTime, IsUser: true);
471 else
472 pSelf->m_UserZoomTarget = CCamera::ZoomStepsToValue(Steps: TargetLevel - 10.0f);
473
474 pSelf->m_AutoSpecCamera = IsReset;
475
476 if(pSelf->GameClient()->m_MultiViewActivated && pSelf->GameClient()->m_Snap.m_SpecInfo.m_Active)
477 pSelf->GameClient()->m_MultiViewPersonalZoom = TargetLevel - 10.0f;
478}
479void CCamera::ConSetView(IConsole::IResult *pResult, void *pUserData)
480{
481 CCamera *pSelf = (CCamera *)pUserData;
482 // wait until free view camera type to update the position
483 pSelf->SetView(Pos: ivec2(pResult->GetInteger(Index: 0), pResult->GetInteger(Index: 1)));
484}
485void CCamera::ConSetViewRelative(IConsole::IResult *pResult, void *pUserData)
486{
487 CCamera *pSelf = (CCamera *)pUserData;
488 // wait until free view camera type to update the position
489 pSelf->SetView(Pos: ivec2(pResult->GetInteger(Index: 0), pResult->GetInteger(Index: 1)), Relative: true);
490}
491void CCamera::ConGotoSwitch(IConsole::IResult *pResult, void *pUserData)
492{
493 CCamera *pSelf = (CCamera *)pUserData;
494 pSelf->GotoSwitch(Number: pResult->GetInteger(Index: 0), Offset: pResult->NumArguments() > 1 ? pResult->GetInteger(Index: 1) : -1);
495}
496void CCamera::ConGotoTele(IConsole::IResult *pResult, void *pUserData)
497{
498 CCamera *pSelf = (CCamera *)pUserData;
499 pSelf->GotoTele(Number: pResult->GetInteger(Index: 0), Offset: pResult->NumArguments() > 1 ? pResult->GetInteger(Index: 1) : -1);
500}
501
502void CCamera::SetView(ivec2 Pos, bool Relative)
503{
504 vec2 RealPos = vec2(Pos.x * 32.0, Pos.y * 32.0);
505 vec2 UntestedViewPos = Relative ? m_ForceFreeviewPos + RealPos : RealPos;
506
507 m_ForceFreeview = true;
508
509 m_ForceFreeviewPos = vec2(
510 std::clamp(val: UntestedViewPos.x, lo: 200.0f, hi: Collision()->GetWidth() * 32 - 200.0f),
511 std::clamp(val: UntestedViewPos.y, lo: 200.0f, hi: Collision()->GetHeight() * 32 - 200.0f));
512}
513
514void CCamera::GotoSwitch(int Number, int Offset)
515{
516 if(Collision()->SwitchLayer() == nullptr)
517 return;
518
519 int Match = -1;
520 ivec2 MatchPos = ivec2(-1, -1);
521
522 auto FindTile = [this, &Match, &MatchPos, &Number, &Offset]() {
523 for(int x = 0; x < Collision()->GetWidth(); x++)
524 {
525 for(int y = 0; y < Collision()->GetHeight(); y++)
526 {
527 int i = y * Collision()->GetWidth() + x;
528 if(Number == Collision()->GetSwitchNumber(Index: i))
529 {
530 Match++;
531 if(Offset != -1)
532 {
533 if(Match == Offset)
534 {
535 MatchPos = ivec2(x, y);
536 m_GotoSwitchOffset = Match;
537 return;
538 }
539 continue;
540 }
541 MatchPos = ivec2(x, y);
542 if(Match == m_GotoSwitchOffset)
543 return;
544 }
545 }
546 }
547 };
548 FindTile();
549
550 if(MatchPos == ivec2(-1, -1))
551 return;
552 if(Match < m_GotoSwitchOffset)
553 m_GotoSwitchOffset = -1;
554 SetView(Pos: MatchPos);
555 m_GotoSwitchOffset++;
556}
557
558void CCamera::GotoTele(int Number, int Offset)
559{
560 if(Collision()->TeleLayer() == nullptr)
561 return;
562 Number--;
563
564 if(m_GotoTeleLastNumber != Number)
565 m_GotoTeleLastPos = ivec2(-1, -1);
566
567 ivec2 MatchPos = ivec2(-1, -1);
568 const size_t NumTeles = Collision()->TeleAllSize(Number);
569 if(!NumTeles)
570 {
571 log_error("camera", "No teleporter with number %d found.", Number + 1);
572 return;
573 }
574
575 if(Offset != -1 || m_GotoTeleLastPos == ivec2(-1, -1))
576 {
577 if((size_t)Offset >= NumTeles || Offset < 0)
578 Offset = 0;
579 vec2 Tele = Collision()->TeleAllGet(Number, Offset);
580 MatchPos = ivec2(Tele.x / 32, Tele.y / 32);
581 m_GotoTeleOffset = Offset;
582 }
583 else
584 {
585 bool FullRound = false;
586 do
587 {
588 vec2 Tele = Collision()->TeleAllGet(Number, Offset: m_GotoTeleOffset);
589 MatchPos = ivec2(Tele.x / 32, Tele.y / 32);
590 m_GotoTeleOffset++;
591 if((size_t)m_GotoTeleOffset >= NumTeles)
592 {
593 m_GotoTeleOffset = 0;
594 if(FullRound)
595 {
596 MatchPos = m_GotoTeleLastPos;
597 break;
598 }
599 FullRound = true;
600 }
601 } while(distance(a: m_GotoTeleLastPos, b: MatchPos) < 10.0f);
602 }
603
604 if(MatchPos == ivec2(-1, -1))
605 return;
606 m_GotoTeleLastPos = MatchPos;
607 m_GotoTeleLastNumber = Number;
608 SetView(Pos: MatchPos);
609}
610
611void CCamera::SetZoom(float Target, int Smoothness, bool IsUser)
612{
613 ChangeZoom(Target, Smoothness, IsUser);
614}
615
616bool CCamera::ZoomAllowed() const
617{
618 return GameClient()->m_Snap.m_SpecInfo.m_Active ||
619 GameClient()->m_GameInfo.m_AllowZoom ||
620 Client()->State() == IClient::STATE_DEMOPLAYBACK;
621}
622
623int CCamera::Deadzone() const
624{
625 return g_Config.m_ClDyncam ? g_Config.m_ClDyncamDeadzone : g_Config.m_ClMouseDeadzone;
626}
627
628int CCamera::FollowFactor() const
629{
630 return g_Config.m_ClDyncam ? g_Config.m_ClDyncamFollowFactor : g_Config.m_ClMouseFollowfactor;
631}
632
633bool CCamera::CanUseAutoSpecCamera() const
634{
635 if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
636 {
637 // only follow mode has the correct camera info
638 return GameClient()->m_Snap.m_SpecInfo.m_HasCameraInfo && GameClient()->m_DemoSpecId == SPEC_FOLLOW;
639 }
640
641 return g_Config.m_ClSpecAutoSync && GameClient()->m_Snap.m_SpecInfo.m_HasCameraInfo &&
642 GameClient()->m_Snap.m_SpecInfo.m_SpectatorId != GameClient()->m_aLocalIds[0] &&
643 (!GameClient()->Client()->DummyConnected() || GameClient()->m_Snap.m_SpecInfo.m_SpectatorId != GameClient()->m_aLocalIds[1]);
644}
645
646void CCamera::ToggleAutoSpecCamera()
647{
648 if(!g_Config.m_ClSpecAutoSync)
649 {
650 g_Config.m_ClSpecAutoSync = 1;
651 GameClient()->m_Camera.m_AutoSpecCamera = true;
652 }
653 else if(GameClient()->m_Camera.m_AutoSpecCamera && GameClient()->m_Camera.SpectatingPlayer() && GameClient()->m_Camera.CanUseAutoSpecCamera())
654 {
655 GameClient()->m_Camera.m_AutoSpecCamera = false;
656 }
657 else
658 {
659 g_Config.m_ClSpecAutoSync = 0;
660 }
661}
662
663void CCamera::UpdateAutoSpecCameraTooltip()
664{
665 const char *pFeatureText = Localize(pStr: "Auto-sync player camera");
666
667 if(!g_Config.m_ClSpecAutoSync)
668 str_format(buffer: m_aAutoSpecCameraTooltip, buffer_size: sizeof(m_aAutoSpecCameraTooltip), format: "%s: %s", pFeatureText, Localize(pStr: "Disabled", pContext: "Auto camera"));
669 else if(!SpectatingPlayer())
670 str_format(buffer: m_aAutoSpecCameraTooltip, buffer_size: sizeof(m_aAutoSpecCameraTooltip), format: "%s: %s", pFeatureText, Localize(pStr: "Enabled", pContext: "Auto camera"));
671 else if(!CanUseAutoSpecCamera())
672 str_format(buffer: m_aAutoSpecCameraTooltip, buffer_size: sizeof(m_aAutoSpecCameraTooltip), format: "%s: %s", pFeatureText, Localize(pStr: "Unavailable for this player", pContext: "Auto camera"));
673 else if(!m_AutoSpecCamera)
674 str_format(buffer: m_aAutoSpecCameraTooltip, buffer_size: sizeof(m_aAutoSpecCameraTooltip), format: "%s: %s", pFeatureText, Localize(pStr: "Inactive", pContext: "Auto camera"));
675 else
676 str_format(buffer: m_aAutoSpecCameraTooltip, buffer_size: sizeof(m_aAutoSpecCameraTooltip), format: "%s: %s", pFeatureText, Localize(pStr: "Active", pContext: "Auto camera"));
677}
678