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 <base/math.h>
4
5#include <engine/client.h>
6#include <engine/shared/config.h>
7
8#include <game/client/components/camera.h>
9#include <game/client/components/chat.h>
10#include <game/client/components/menus.h>
11#include <game/client/components/scoreboard.h>
12#include <game/client/gameclient.h>
13#include <game/collision.h>
14
15#include <base/vmath.h>
16
17#include "controls.h"
18
19CControls::CControls()
20{
21 mem_zero(block: &m_aLastData, size: sizeof(m_aLastData));
22 mem_zero(block: m_aMousePos, size: sizeof(m_aMousePos));
23 mem_zero(block: m_aMousePosOnAction, size: sizeof(m_aMousePosOnAction));
24 mem_zero(block: m_aTargetPos, size: sizeof(m_aTargetPos));
25}
26
27void CControls::OnReset()
28{
29 ResetInput(Dummy: 0);
30 ResetInput(Dummy: 1);
31
32 for(int &AmmoCount : m_aAmmoCount)
33 AmmoCount = 0;
34
35 m_LastSendTime = 0;
36}
37
38void CControls::ResetInput(int Dummy)
39{
40 m_aLastData[Dummy].m_Direction = 0;
41 // simulate releasing the fire button
42 if((m_aLastData[Dummy].m_Fire & 1) != 0)
43 m_aLastData[Dummy].m_Fire++;
44 m_aLastData[Dummy].m_Fire &= INPUT_STATE_MASK;
45 m_aLastData[Dummy].m_Jump = 0;
46 m_aInputData[Dummy] = m_aLastData[Dummy];
47
48 m_aInputDirectionLeft[Dummy] = 0;
49 m_aInputDirectionRight[Dummy] = 0;
50}
51
52void CControls::OnPlayerDeath()
53{
54 for(int &AmmoCount : m_aAmmoCount)
55 AmmoCount = 0;
56}
57
58struct CInputState
59{
60 CControls *m_pControls;
61 int *m_apVariables[NUM_DUMMIES];
62};
63
64static void ConKeyInputState(IConsole::IResult *pResult, void *pUserData)
65{
66 CInputState *pState = (CInputState *)pUserData;
67
68 if(pState->m_pControls->GameClient()->m_GameInfo.m_BugDDRaceInput && pState->m_pControls->GameClient()->m_Snap.m_SpecInfo.m_Active)
69 return;
70
71 *pState->m_apVariables[g_Config.m_ClDummy] = pResult->GetInteger(Index: 0);
72}
73
74static void ConKeyInputCounter(IConsole::IResult *pResult, void *pUserData)
75{
76 CInputState *pState = (CInputState *)pUserData;
77
78 if(pState->m_pControls->GameClient()->m_GameInfo.m_BugDDRaceInput && pState->m_pControls->GameClient()->m_Snap.m_SpecInfo.m_Active)
79 return;
80
81 int *pVariable = pState->m_apVariables[g_Config.m_ClDummy];
82 if(((*pVariable) & 1) != pResult->GetInteger(Index: 0))
83 (*pVariable)++;
84 *pVariable &= INPUT_STATE_MASK;
85}
86
87struct CInputSet
88{
89 CControls *m_pControls;
90 int *m_apVariables[NUM_DUMMIES];
91 int m_Value;
92};
93
94static void ConKeyInputSet(IConsole::IResult *pResult, void *pUserData)
95{
96 CInputSet *pSet = (CInputSet *)pUserData;
97 if(pResult->GetInteger(Index: 0))
98 {
99 *pSet->m_apVariables[g_Config.m_ClDummy] = pSet->m_Value;
100 }
101}
102
103static void ConKeyInputNextPrevWeapon(IConsole::IResult *pResult, void *pUserData)
104{
105 CInputSet *pSet = (CInputSet *)pUserData;
106 ConKeyInputCounter(pResult, pUserData: pSet);
107 pSet->m_pControls->m_aInputData[g_Config.m_ClDummy].m_WantedWeapon = 0;
108}
109
110void CControls::OnConsoleInit()
111{
112 // game commands
113 {
114 static CInputState s_State = {.m_pControls: this, .m_apVariables: {&m_aInputDirectionLeft[0], &m_aInputDirectionLeft[1]}};
115 Console()->Register(pName: "+left", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputState, pUser: &s_State, pHelp: "Move left");
116 }
117 {
118 static CInputState s_State = {.m_pControls: this, .m_apVariables: {&m_aInputDirectionRight[0], &m_aInputDirectionRight[1]}};
119 Console()->Register(pName: "+right", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputState, pUser: &s_State, pHelp: "Move right");
120 }
121 {
122 static CInputState s_State = {.m_pControls: this, .m_apVariables: {&m_aInputData[0].m_Jump, &m_aInputData[1].m_Jump}};
123 Console()->Register(pName: "+jump", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputState, pUser: &s_State, pHelp: "Jump");
124 }
125 {
126 static CInputState s_State = {.m_pControls: this, .m_apVariables: {&m_aInputData[0].m_Hook, &m_aInputData[1].m_Hook}};
127 Console()->Register(pName: "+hook", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputState, pUser: &s_State, pHelp: "Hook");
128 }
129 {
130 static CInputState s_State = {.m_pControls: this, .m_apVariables: {&m_aInputData[0].m_Fire, &m_aInputData[1].m_Fire}};
131 Console()->Register(pName: "+fire", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputCounter, pUser: &s_State, pHelp: "Fire");
132 }
133 {
134 static CInputState s_State = {.m_pControls: this, .m_apVariables: {&m_aShowHookColl[0], &m_aShowHookColl[1]}};
135 Console()->Register(pName: "+showhookcoll", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputState, pUser: &s_State, pHelp: "Show Hook Collision");
136 }
137
138 {
139 static CInputSet s_Set = {.m_pControls: this, .m_apVariables: {&m_aInputData[0].m_WantedWeapon, &m_aInputData[1].m_WantedWeapon}, .m_Value: 1};
140 Console()->Register(pName: "+weapon1", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputSet, pUser: &s_Set, pHelp: "Switch to hammer");
141 }
142 {
143 static CInputSet s_Set = {.m_pControls: this, .m_apVariables: {&m_aInputData[0].m_WantedWeapon, &m_aInputData[1].m_WantedWeapon}, .m_Value: 2};
144 Console()->Register(pName: "+weapon2", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputSet, pUser: &s_Set, pHelp: "Switch to gun");
145 }
146 {
147 static CInputSet s_Set = {.m_pControls: this, .m_apVariables: {&m_aInputData[0].m_WantedWeapon, &m_aInputData[1].m_WantedWeapon}, .m_Value: 3};
148 Console()->Register(pName: "+weapon3", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputSet, pUser: &s_Set, pHelp: "Switch to shotgun");
149 }
150 {
151 static CInputSet s_Set = {.m_pControls: this, .m_apVariables: {&m_aInputData[0].m_WantedWeapon, &m_aInputData[1].m_WantedWeapon}, .m_Value: 4};
152 Console()->Register(pName: "+weapon4", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputSet, pUser: &s_Set, pHelp: "Switch to grenade");
153 }
154 {
155 static CInputSet s_Set = {.m_pControls: this, .m_apVariables: {&m_aInputData[0].m_WantedWeapon, &m_aInputData[1].m_WantedWeapon}, .m_Value: 5};
156 Console()->Register(pName: "+weapon5", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputSet, pUser: &s_Set, pHelp: "Switch to laser");
157 }
158
159 {
160 static CInputSet s_Set = {.m_pControls: this, .m_apVariables: {&m_aInputData[0].m_NextWeapon, &m_aInputData[1].m_NextWeapon}, .m_Value: 0};
161 Console()->Register(pName: "+nextweapon", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputNextPrevWeapon, pUser: &s_Set, pHelp: "Switch to next weapon");
162 }
163 {
164 static CInputSet s_Set = {.m_pControls: this, .m_apVariables: {&m_aInputData[0].m_PrevWeapon, &m_aInputData[1].m_PrevWeapon}, .m_Value: 0};
165 Console()->Register(pName: "+prevweapon", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConKeyInputNextPrevWeapon, pUser: &s_Set, pHelp: "Switch to previous weapon");
166 }
167}
168
169void CControls::OnMessage(int Msg, void *pRawMsg)
170{
171 if(Msg == NETMSGTYPE_SV_WEAPONPICKUP)
172 {
173 CNetMsg_Sv_WeaponPickup *pMsg = (CNetMsg_Sv_WeaponPickup *)pRawMsg;
174 if(g_Config.m_ClAutoswitchWeapons)
175 m_aInputData[g_Config.m_ClDummy].m_WantedWeapon = pMsg->m_Weapon + 1;
176 // We don't really know ammo count, until we'll switch to that weapon, but any non-zero count will suffice here
177 m_aAmmoCount[pMsg->m_Weapon % NUM_WEAPONS] = 10;
178 }
179}
180
181int CControls::SnapInput(int *pData)
182{
183 // update player state
184 if(m_pClient->m_Chat.IsActive())
185 m_aInputData[g_Config.m_ClDummy].m_PlayerFlags = PLAYERFLAG_CHATTING;
186 else if(m_pClient->m_Menus.IsActive())
187 m_aInputData[g_Config.m_ClDummy].m_PlayerFlags = PLAYERFLAG_IN_MENU;
188 else
189 m_aInputData[g_Config.m_ClDummy].m_PlayerFlags = PLAYERFLAG_PLAYING;
190
191 if(m_pClient->m_Scoreboard.Active())
192 m_aInputData[g_Config.m_ClDummy].m_PlayerFlags |= PLAYERFLAG_SCOREBOARD;
193
194 if(m_pClient->m_Controls.m_aShowHookColl[g_Config.m_ClDummy])
195 m_aInputData[g_Config.m_ClDummy].m_PlayerFlags |= PLAYERFLAG_AIM;
196
197 bool Send = m_aLastData[g_Config.m_ClDummy].m_PlayerFlags != m_aInputData[g_Config.m_ClDummy].m_PlayerFlags;
198
199 m_aLastData[g_Config.m_ClDummy].m_PlayerFlags = m_aInputData[g_Config.m_ClDummy].m_PlayerFlags;
200
201 // we freeze the input if chat or menu is activated
202 if(!(m_aInputData[g_Config.m_ClDummy].m_PlayerFlags & PLAYERFLAG_PLAYING))
203 {
204 if(!GameClient()->m_GameInfo.m_BugDDRaceInput)
205 ResetInput(Dummy: g_Config.m_ClDummy);
206
207 mem_copy(dest: pData, source: &m_aInputData[g_Config.m_ClDummy], size: sizeof(m_aInputData[0]));
208
209 // set the target anyway though so that we can keep seeing our surroundings,
210 // even if chat or menu are activated
211 m_aInputData[g_Config.m_ClDummy].m_TargetX = (int)m_aMousePos[g_Config.m_ClDummy].x;
212 m_aInputData[g_Config.m_ClDummy].m_TargetY = (int)m_aMousePos[g_Config.m_ClDummy].y;
213
214 // send once a second just to be sure
215 Send = Send || time_get() > m_LastSendTime + time_freq();
216 }
217 else
218 {
219 m_aInputData[g_Config.m_ClDummy].m_TargetX = (int)m_aMousePos[g_Config.m_ClDummy].x;
220 m_aInputData[g_Config.m_ClDummy].m_TargetY = (int)m_aMousePos[g_Config.m_ClDummy].y;
221
222 if(g_Config.m_ClSubTickAiming && m_aMousePosOnAction[g_Config.m_ClDummy] != vec2(0.0f, 0.0f))
223 {
224 m_aInputData[g_Config.m_ClDummy].m_TargetX = (int)m_aMousePosOnAction[g_Config.m_ClDummy].x;
225 m_aInputData[g_Config.m_ClDummy].m_TargetY = (int)m_aMousePosOnAction[g_Config.m_ClDummy].y;
226 m_aMousePosOnAction[g_Config.m_ClDummy] = vec2(0.0f, 0.0f);
227 }
228
229 if(!m_aInputData[g_Config.m_ClDummy].m_TargetX && !m_aInputData[g_Config.m_ClDummy].m_TargetY)
230 {
231 m_aInputData[g_Config.m_ClDummy].m_TargetX = 1;
232 m_aMousePos[g_Config.m_ClDummy].x = 1;
233 }
234
235 // set direction
236 m_aInputData[g_Config.m_ClDummy].m_Direction = 0;
237 if(m_aInputDirectionLeft[g_Config.m_ClDummy] && !m_aInputDirectionRight[g_Config.m_ClDummy])
238 m_aInputData[g_Config.m_ClDummy].m_Direction = -1;
239 if(!m_aInputDirectionLeft[g_Config.m_ClDummy] && m_aInputDirectionRight[g_Config.m_ClDummy])
240 m_aInputData[g_Config.m_ClDummy].m_Direction = 1;
241
242 // scale TargetX, TargetY by zoom.
243 if(!m_pClient->m_Snap.m_SpecInfo.m_Active)
244 {
245 m_aInputData[g_Config.m_ClDummy].m_TargetX *= m_pClient->m_Camera.m_Zoom;
246 m_aInputData[g_Config.m_ClDummy].m_TargetY *= m_pClient->m_Camera.m_Zoom;
247 }
248
249 // dummy copy moves
250 if(g_Config.m_ClDummyCopyMoves)
251 {
252 CNetObj_PlayerInput *pDummyInput = &m_pClient->m_DummyInput;
253 pDummyInput->m_Direction = m_aInputData[g_Config.m_ClDummy].m_Direction;
254 pDummyInput->m_Hook = m_aInputData[g_Config.m_ClDummy].m_Hook;
255 pDummyInput->m_Jump = m_aInputData[g_Config.m_ClDummy].m_Jump;
256 pDummyInput->m_PlayerFlags = m_aInputData[g_Config.m_ClDummy].m_PlayerFlags;
257 pDummyInput->m_TargetX = m_aInputData[g_Config.m_ClDummy].m_TargetX;
258 pDummyInput->m_TargetY = m_aInputData[g_Config.m_ClDummy].m_TargetY;
259 pDummyInput->m_WantedWeapon = m_aInputData[g_Config.m_ClDummy].m_WantedWeapon;
260
261 if(!g_Config.m_ClDummyControl)
262 pDummyInput->m_Fire += m_aInputData[g_Config.m_ClDummy].m_Fire - m_aLastData[g_Config.m_ClDummy].m_Fire;
263
264 pDummyInput->m_NextWeapon += m_aInputData[g_Config.m_ClDummy].m_NextWeapon - m_aLastData[g_Config.m_ClDummy].m_NextWeapon;
265 pDummyInput->m_PrevWeapon += m_aInputData[g_Config.m_ClDummy].m_PrevWeapon - m_aLastData[g_Config.m_ClDummy].m_PrevWeapon;
266
267 m_aInputData[!g_Config.m_ClDummy] = *pDummyInput;
268 }
269
270 if(g_Config.m_ClDummyControl)
271 {
272 CNetObj_PlayerInput *pDummyInput = &m_pClient->m_DummyInput;
273 pDummyInput->m_Jump = g_Config.m_ClDummyJump;
274
275 if(g_Config.m_ClDummyFire)
276 pDummyInput->m_Fire = g_Config.m_ClDummyFire;
277 else if((pDummyInput->m_Fire & 1) != 0)
278 pDummyInput->m_Fire++;
279
280 pDummyInput->m_Hook = g_Config.m_ClDummyHook;
281 }
282
283 // stress testing
284#ifdef CONF_DEBUG
285 if(g_Config.m_DbgStress)
286 {
287 float t = Client()->LocalTime();
288 mem_zero(block: &m_aInputData[g_Config.m_ClDummy], size: sizeof(m_aInputData[0]));
289
290 m_aInputData[g_Config.m_ClDummy].m_Direction = ((int)t / 2) & 1;
291 m_aInputData[g_Config.m_ClDummy].m_Jump = ((int)t);
292 m_aInputData[g_Config.m_ClDummy].m_Fire = ((int)(t * 10));
293 m_aInputData[g_Config.m_ClDummy].m_Hook = ((int)(t * 2)) & 1;
294 m_aInputData[g_Config.m_ClDummy].m_WantedWeapon = ((int)t) % NUM_WEAPONS;
295 m_aInputData[g_Config.m_ClDummy].m_TargetX = (int)(std::sin(x: t * 3) * 100.0f);
296 m_aInputData[g_Config.m_ClDummy].m_TargetY = (int)(std::cos(x: t * 3) * 100.0f);
297 }
298#endif
299 // check if we need to send input
300 Send = Send || m_aInputData[g_Config.m_ClDummy].m_Direction != m_aLastData[g_Config.m_ClDummy].m_Direction;
301 Send = Send || m_aInputData[g_Config.m_ClDummy].m_Jump != m_aLastData[g_Config.m_ClDummy].m_Jump;
302 Send = Send || m_aInputData[g_Config.m_ClDummy].m_Fire != m_aLastData[g_Config.m_ClDummy].m_Fire;
303 Send = Send || m_aInputData[g_Config.m_ClDummy].m_Hook != m_aLastData[g_Config.m_ClDummy].m_Hook;
304 Send = Send || m_aInputData[g_Config.m_ClDummy].m_WantedWeapon != m_aLastData[g_Config.m_ClDummy].m_WantedWeapon;
305 Send = Send || m_aInputData[g_Config.m_ClDummy].m_NextWeapon != m_aLastData[g_Config.m_ClDummy].m_NextWeapon;
306 Send = Send || m_aInputData[g_Config.m_ClDummy].m_PrevWeapon != m_aLastData[g_Config.m_ClDummy].m_PrevWeapon;
307 Send = Send || time_get() > m_LastSendTime + time_freq() / 25; // send at least 10hz
308 Send = Send || (m_pClient->m_Snap.m_pLocalCharacter && m_pClient->m_Snap.m_pLocalCharacter->m_Weapon == WEAPON_NINJA && (m_aInputData[g_Config.m_ClDummy].m_Direction || m_aInputData[g_Config.m_ClDummy].m_Jump || m_aInputData[g_Config.m_ClDummy].m_Hook));
309 }
310
311 // copy and return size
312 m_aLastData[g_Config.m_ClDummy] = m_aInputData[g_Config.m_ClDummy];
313
314 if(!Send)
315 return 0;
316
317 m_LastSendTime = time_get();
318 mem_copy(dest: pData, source: &m_aInputData[g_Config.m_ClDummy], size: sizeof(m_aInputData[0]));
319 return sizeof(m_aInputData[0]);
320}
321
322void CControls::OnRender()
323{
324 if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
325 return;
326
327 if(g_Config.m_ClAutoswitchWeaponsOutOfAmmo && !GameClient()->m_GameInfo.m_UnlimitedAmmo && m_pClient->m_Snap.m_pLocalCharacter)
328 {
329 // Keep track of ammo count, we know weapon ammo only when we switch to that weapon, this is tracked on server and protocol does not track that
330 m_aAmmoCount[m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS] = m_pClient->m_Snap.m_pLocalCharacter->m_AmmoCount;
331 // Autoswitch weapon if we're out of ammo
332 if(m_aInputData[g_Config.m_ClDummy].m_Fire % 2 != 0 &&
333 m_pClient->m_Snap.m_pLocalCharacter->m_AmmoCount == 0 &&
334 m_pClient->m_Snap.m_pLocalCharacter->m_Weapon != WEAPON_HAMMER &&
335 m_pClient->m_Snap.m_pLocalCharacter->m_Weapon != WEAPON_NINJA)
336 {
337 int Weapon;
338 for(Weapon = WEAPON_LASER; Weapon > WEAPON_GUN; Weapon--)
339 {
340 if(Weapon == m_pClient->m_Snap.m_pLocalCharacter->m_Weapon)
341 continue;
342 if(m_aAmmoCount[Weapon] > 0)
343 break;
344 }
345 if(Weapon != m_pClient->m_Snap.m_pLocalCharacter->m_Weapon)
346 m_aInputData[g_Config.m_ClDummy].m_WantedWeapon = Weapon + 1;
347 }
348 }
349
350 // update target pos
351 if(m_pClient->m_Snap.m_pGameInfoObj && !m_pClient->m_Snap.m_SpecInfo.m_Active)
352 m_aTargetPos[g_Config.m_ClDummy] = m_pClient->m_LocalCharacterPos + m_aMousePos[g_Config.m_ClDummy];
353 else if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_UsePosition)
354 m_aTargetPos[g_Config.m_ClDummy] = m_pClient->m_Snap.m_SpecInfo.m_Position + m_aMousePos[g_Config.m_ClDummy];
355 else
356 m_aTargetPos[g_Config.m_ClDummy] = m_aMousePos[g_Config.m_ClDummy];
357}
358
359bool CControls::OnCursorMove(float x, float y, IInput::ECursorType CursorType)
360{
361 if(m_pClient->m_Snap.m_pGameInfoObj && (m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED))
362 return false;
363
364 if(CursorType == IInput::CURSOR_JOYSTICK && g_Config.m_InpControllerAbsolute && m_pClient->m_Snap.m_pGameInfoObj && !m_pClient->m_Snap.m_SpecInfo.m_Active)
365 {
366 vec2 AbsoluteDirection;
367 if(Input()->GetActiveJoystick()->Absolute(pX: &AbsoluteDirection.x, pY: &AbsoluteDirection.y))
368 m_aMousePos[g_Config.m_ClDummy] = AbsoluteDirection * GetMaxMouseDistance();
369 return true;
370 }
371
372 float Factor = 1.0f;
373 if(g_Config.m_ClDyncam && g_Config.m_ClDyncamMousesens)
374 {
375 Factor = g_Config.m_ClDyncamMousesens / 100.0f;
376 }
377 else
378 {
379 switch(CursorType)
380 {
381 case IInput::CURSOR_MOUSE:
382 Factor = g_Config.m_InpMousesens / 100.0f;
383 break;
384 case IInput::CURSOR_JOYSTICK:
385 Factor = g_Config.m_InpControllerSens / 100.0f;
386 break;
387 default:
388 dbg_msg(sys: "assert", fmt: "CControls::OnCursorMove CursorType %d", (int)CursorType);
389 dbg_break();
390 break;
391 }
392 }
393
394 if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorId < 0)
395 Factor *= m_pClient->m_Camera.m_Zoom;
396
397 m_aMousePos[g_Config.m_ClDummy] += vec2(x, y) * Factor;
398 ClampMousePos();
399 return true;
400}
401
402void CControls::ClampMousePos()
403{
404 if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorId < 0)
405 {
406 m_aMousePos[g_Config.m_ClDummy].x = clamp(val: m_aMousePos[g_Config.m_ClDummy].x, lo: -201.0f * 32, hi: (Collision()->GetWidth() + 201.0f) * 32.0f);
407 m_aMousePos[g_Config.m_ClDummy].y = clamp(val: m_aMousePos[g_Config.m_ClDummy].y, lo: -201.0f * 32, hi: (Collision()->GetHeight() + 201.0f) * 32.0f);
408 }
409 else
410 {
411 const float MouseMin = GetMinMouseDistance();
412 const float MouseMax = GetMaxMouseDistance();
413
414 float MouseDistance = length(a: m_aMousePos[g_Config.m_ClDummy]);
415 if(MouseDistance < 0.001f)
416 {
417 m_aMousePos[g_Config.m_ClDummy].x = 0.001f;
418 m_aMousePos[g_Config.m_ClDummy].y = 0;
419 MouseDistance = 0.001f;
420 }
421 if(MouseDistance < MouseMin)
422 m_aMousePos[g_Config.m_ClDummy] = normalize_pre_length(v: m_aMousePos[g_Config.m_ClDummy], len: MouseDistance) * MouseMin;
423 MouseDistance = length(a: m_aMousePos[g_Config.m_ClDummy]);
424 if(MouseDistance > MouseMax)
425 m_aMousePos[g_Config.m_ClDummy] = normalize_pre_length(v: m_aMousePos[g_Config.m_ClDummy], len: MouseDistance) * MouseMax;
426 }
427}
428
429float CControls::GetMinMouseDistance() const
430{
431 return g_Config.m_ClDyncam ? g_Config.m_ClDyncamMinDistance : g_Config.m_ClMouseMinDistance;
432}
433
434float CControls::GetMaxMouseDistance() const
435{
436 float CameraMaxDistance = 200.0f;
437 float FollowFactor = (g_Config.m_ClDyncam ? g_Config.m_ClDyncamFollowFactor : g_Config.m_ClMouseFollowfactor) / 100.0f;
438 float DeadZone = g_Config.m_ClDyncam ? g_Config.m_ClDyncamDeadzone : g_Config.m_ClMouseDeadzone;
439 float MaxDistance = g_Config.m_ClDyncam ? g_Config.m_ClDyncamMaxDistance : g_Config.m_ClMouseMaxDistance;
440 return minimum(a: (FollowFactor != 0 ? CameraMaxDistance / FollowFactor + DeadZone : MaxDistance), b: MaxDistance);
441}
442