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 | |
19 | CControls::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 | |
27 | void 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 | |
38 | void 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 | |
52 | void CControls::OnPlayerDeath() |
53 | { |
54 | for(int &AmmoCount : m_aAmmoCount) |
55 | AmmoCount = 0; |
56 | } |
57 | |
58 | struct CInputState |
59 | { |
60 | CControls *m_pControls; |
61 | int *m_apVariables[NUM_DUMMIES]; |
62 | }; |
63 | |
64 | static 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 | |
74 | static 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 | |
87 | struct CInputSet |
88 | { |
89 | CControls *m_pControls; |
90 | int *m_apVariables[NUM_DUMMIES]; |
91 | int m_Value; |
92 | }; |
93 | |
94 | static 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 | |
103 | static 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 | |
110 | void 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 | |
169 | void 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 | |
181 | int 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 | |
322 | void 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 | |
359 | bool 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 | |
402 | void 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 | |
429 | float CControls::GetMinMouseDistance() const |
430 | { |
431 | return g_Config.m_ClDyncam ? g_Config.m_ClDyncamMinDistance : g_Config.m_ClMouseMinDistance; |
432 | } |
433 | |
434 | float 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 | |