/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "controls.h" enum { LEFT_JOYSTICK_X = 0, LEFT_JOYSTICK_Y = 1, RIGHT_JOYSTICK_X = 2, RIGHT_JOYSTICK_Y = 3, SECOND_RIGHT_JOYSTICK_X = 20, SECOND_RIGHT_JOYSTICK_Y = 21, NUM_JOYSTICK_AXES = 22 }; CControls::CControls() { mem_zero(&m_LastData, sizeof(m_LastData)); m_LastDummy = 0; m_OtherFire = 0; if(g_Config.m_InpJoystick) { SDL_Init(SDL_INIT_JOYSTICK); m_Joystick = SDL_JoystickOpen(0); if(m_Joystick && SDL_JoystickNumAxes(m_Joystick) < NUM_JOYSTICK_AXES) { SDL_JoystickClose(m_Joystick); m_Joystick = NULL; } m_Gamepad = SDL_JoystickOpen(2); SDL_JoystickEventState(SDL_QUERY); m_UsingGamepad = false; #if defined(CONF_FAMILY_UNIX) if(getenv("OUYA")) m_UsingGamepad = true; #endif } else { m_Joystick = NULL; m_Gamepad = NULL; m_UsingGamepad = false; } } void CControls::OnReset() { ResetInput(0); ResetInput(1); m_JoystickFirePressed = false; m_JoystickRunPressed = false; m_JoystickTapTime = 0; for(int &AmmoCount : m_AmmoCount) AmmoCount = 0; m_OldMouseX = m_OldMouseY = 0.0f; } void CControls::ResetInput(int Dummy) { m_LastData[Dummy].m_Direction = 0; //m_LastData[Dummy].m_Hook = 0; // simulate releasing the fire button if((m_LastData[Dummy].m_Fire & 1) != 0) m_LastData[Dummy].m_Fire++; m_LastData[Dummy].m_Fire &= INPUT_STATE_MASK; m_LastData[Dummy].m_Jump = 0; m_InputData[Dummy] = m_LastData[Dummy]; m_InputDirectionLeft[Dummy] = 0; m_InputDirectionRight[Dummy] = 0; } void CControls::OnRelease() { //OnReset(); } void CControls::OnPlayerDeath() { if(g_Config.m_ClResetWantedWeaponOnDeath) m_LastData[g_Config.m_ClDummy].m_WantedWeapon = m_InputData[g_Config.m_ClDummy].m_WantedWeapon = 0; for(int &AmmoCount : m_AmmoCount) AmmoCount = 0; m_JoystickTapTime = 0; // Do not launch hook on first tap } struct CInputState { CControls *m_pControls; int *m_pVariable1; int *m_pVariable2; }; static void ConKeyInputState(IConsole::IResult *pResult, void *pUserData) { CInputState *pState = (CInputState *)pUserData; if(pState->m_pControls->GameClient()->m_GameInfo.m_BugDDRaceInput && pState->m_pControls->GameClient()->m_Snap.m_SpecInfo.m_Active) return; if(g_Config.m_ClDummy) *pState->m_pVariable2 = pResult->GetInteger(0); else *pState->m_pVariable1 = pResult->GetInteger(0); } static void ConKeyInputCounter(IConsole::IResult *pResult, void *pUserData) { CInputState *pState = (CInputState *)pUserData; if(pState->m_pControls->GameClient()->m_GameInfo.m_BugDDRaceInput && pState->m_pControls->GameClient()->m_Snap.m_SpecInfo.m_Active) return; int *v; if(g_Config.m_ClDummy) v = pState->m_pVariable2; else v = pState->m_pVariable1; if(((*v) & 1) != pResult->GetInteger(0)) (*v)++; *v &= INPUT_STATE_MASK; } struct CInputSet { CControls *m_pControls; int *m_pVariable1; int *m_pVariable2; int m_Value; }; static void ConKeyInputSet(IConsole::IResult *pResult, void *pUserData) { CInputSet *pSet = (CInputSet *)pUserData; if(pResult->GetInteger(0)) { if(g_Config.m_ClDummy) *pSet->m_pVariable2 = pSet->m_Value; else *pSet->m_pVariable1 = pSet->m_Value; } } static void ConKeyInputNextPrevWeapon(IConsole::IResult *pResult, void *pUserData) { CInputSet *pSet = (CInputSet *)pUserData; ConKeyInputCounter(pResult, pSet); pSet->m_pControls->m_InputData[g_Config.m_ClDummy].m_WantedWeapon = 0; } void CControls::OnConsoleInit() { // game commands { static CInputState s_State = {this, &m_InputDirectionLeft[0], &m_InputDirectionLeft[1]}; Console()->Register("+left", "", CFGFLAG_CLIENT, ConKeyInputState, (void *)&s_State, "Move left"); } { static CInputState s_State = {this, &m_InputDirectionRight[0], &m_InputDirectionRight[1]}; Console()->Register("+right", "", CFGFLAG_CLIENT, ConKeyInputState, (void *)&s_State, "Move right"); } { static CInputState s_State = {this, &m_InputData[0].m_Jump, &m_InputData[1].m_Jump}; Console()->Register("+jump", "", CFGFLAG_CLIENT, ConKeyInputState, (void *)&s_State, "Jump"); } { static CInputState s_State = {this, &m_InputData[0].m_Hook, &m_InputData[1].m_Hook}; Console()->Register("+hook", "", CFGFLAG_CLIENT, ConKeyInputState, (void *)&s_State, "Hook"); } { static CInputState s_State = {this, &m_InputData[0].m_Fire, &m_InputData[1].m_Fire}; Console()->Register("+fire", "", CFGFLAG_CLIENT, ConKeyInputCounter, (void *)&s_State, "Fire"); } { static CInputState s_State = {this, &m_ShowHookColl[0], &m_ShowHookColl[1]}; Console()->Register("+showhookcoll", "", CFGFLAG_CLIENT, ConKeyInputState, (void *)&s_State, "Show Hook Collision"); } { static CInputSet s_Set = {this, &m_InputData[0].m_WantedWeapon, &m_InputData[1].m_WantedWeapon, 1}; Console()->Register("+weapon1", "", CFGFLAG_CLIENT, ConKeyInputSet, (void *)&s_Set, "Switch to hammer"); } { static CInputSet s_Set = {this, &m_InputData[0].m_WantedWeapon, &m_InputData[1].m_WantedWeapon, 2}; Console()->Register("+weapon2", "", CFGFLAG_CLIENT, ConKeyInputSet, (void *)&s_Set, "Switch to gun"); } { static CInputSet s_Set = {this, &m_InputData[0].m_WantedWeapon, &m_InputData[1].m_WantedWeapon, 3}; Console()->Register("+weapon3", "", CFGFLAG_CLIENT, ConKeyInputSet, (void *)&s_Set, "Switch to shotgun"); } { static CInputSet s_Set = {this, &m_InputData[0].m_WantedWeapon, &m_InputData[1].m_WantedWeapon, 4}; Console()->Register("+weapon4", "", CFGFLAG_CLIENT, ConKeyInputSet, (void *)&s_Set, "Switch to grenade"); } { static CInputSet s_Set = {this, &m_InputData[0].m_WantedWeapon, &m_InputData[1].m_WantedWeapon, 5}; Console()->Register("+weapon5", "", CFGFLAG_CLIENT, ConKeyInputSet, (void *)&s_Set, "Switch to laser"); } { static CInputSet s_Set = {this, &m_InputData[0].m_NextWeapon, &m_InputData[1].m_NextWeapon, 0}; Console()->Register("+nextweapon", "", CFGFLAG_CLIENT, ConKeyInputNextPrevWeapon, (void *)&s_Set, "Switch to next weapon"); } { static CInputSet s_Set = {this, &m_InputData[0].m_PrevWeapon, &m_InputData[1].m_PrevWeapon, 0}; Console()->Register("+prevweapon", "", CFGFLAG_CLIENT, ConKeyInputNextPrevWeapon, (void *)&s_Set, "Switch to previous weapon"); } } void CControls::OnMessage(int Msg, void *pRawMsg) { if(Msg == NETMSGTYPE_SV_WEAPONPICKUP) { CNetMsg_Sv_WeaponPickup *pMsg = (CNetMsg_Sv_WeaponPickup *)pRawMsg; if(g_Config.m_ClAutoswitchWeapons) m_InputData[g_Config.m_ClDummy].m_WantedWeapon = pMsg->m_Weapon + 1; // We don't really know ammo count, until we'll switch to that weapon, but any non-zero count will suffice here m_AmmoCount[pMsg->m_Weapon % NUM_WEAPONS] = 10; } } int CControls::SnapInput(int *pData) { static int64 LastSendTime = 0; bool Send = false; // update player state if(m_pClient->m_pChat->IsActive()) m_InputData[g_Config.m_ClDummy].m_PlayerFlags = PLAYERFLAG_CHATTING; else if(m_pClient->m_pMenus->IsActive()) m_InputData[g_Config.m_ClDummy].m_PlayerFlags = PLAYERFLAG_IN_MENU; else { if(m_InputData[g_Config.m_ClDummy].m_PlayerFlags == PLAYERFLAG_CHATTING) { if(GameClient()->m_GameInfo.m_BugDDRaceInput) ResetInput(g_Config.m_ClDummy); } m_InputData[g_Config.m_ClDummy].m_PlayerFlags = PLAYERFLAG_PLAYING; } if(m_pClient->m_pScoreboard->Active()) m_InputData[g_Config.m_ClDummy].m_PlayerFlags |= PLAYERFLAG_SCOREBOARD; if(m_InputData[g_Config.m_ClDummy].m_PlayerFlags != PLAYERFLAG_PLAYING) m_JoystickTapTime = 0; // Do not launch hook on first tap if(m_pClient->m_pControls->m_ShowHookColl[g_Config.m_ClDummy]) m_InputData[g_Config.m_ClDummy].m_PlayerFlags |= PLAYERFLAG_AIM; if(m_LastData[g_Config.m_ClDummy].m_PlayerFlags != m_InputData[g_Config.m_ClDummy].m_PlayerFlags) Send = true; m_LastData[g_Config.m_ClDummy].m_PlayerFlags = m_InputData[g_Config.m_ClDummy].m_PlayerFlags; // we freeze the input if chat or menu is activated if(!(m_InputData[g_Config.m_ClDummy].m_PlayerFlags & PLAYERFLAG_PLAYING)) { if(!GameClient()->m_GameInfo.m_BugDDRaceInput) ResetInput(g_Config.m_ClDummy); mem_copy(pData, &m_InputData[g_Config.m_ClDummy], sizeof(m_InputData[0])); // set the target anyway though so that we can keep seeing our surroundings, // even if chat or menu are activated m_InputData[g_Config.m_ClDummy].m_TargetX = (int)m_MousePos[g_Config.m_ClDummy].x; m_InputData[g_Config.m_ClDummy].m_TargetY = (int)m_MousePos[g_Config.m_ClDummy].y; // send once a second just to be sure if(time_get() > LastSendTime + time_freq()) Send = true; } else { m_InputData[g_Config.m_ClDummy].m_TargetX = (int)m_MousePos[g_Config.m_ClDummy].x; m_InputData[g_Config.m_ClDummy].m_TargetY = (int)m_MousePos[g_Config.m_ClDummy].y; if(!m_InputData[g_Config.m_ClDummy].m_TargetX && !m_InputData[g_Config.m_ClDummy].m_TargetY) { m_InputData[g_Config.m_ClDummy].m_TargetX = 1; m_MousePos[g_Config.m_ClDummy].x = 1; } // set direction m_InputData[g_Config.m_ClDummy].m_Direction = 0; if(m_InputDirectionLeft[g_Config.m_ClDummy] && !m_InputDirectionRight[g_Config.m_ClDummy]) m_InputData[g_Config.m_ClDummy].m_Direction = -1; if(!m_InputDirectionLeft[g_Config.m_ClDummy] && m_InputDirectionRight[g_Config.m_ClDummy]) m_InputData[g_Config.m_ClDummy].m_Direction = 1; // dummy copy moves if(g_Config.m_ClDummyCopyMoves) { CNetObj_PlayerInput *pDummyInput = &m_pClient->m_DummyInput; pDummyInput->m_Direction = m_InputData[g_Config.m_ClDummy].m_Direction; pDummyInput->m_Hook = m_InputData[g_Config.m_ClDummy].m_Hook; pDummyInput->m_Jump = m_InputData[g_Config.m_ClDummy].m_Jump; pDummyInput->m_PlayerFlags = m_InputData[g_Config.m_ClDummy].m_PlayerFlags; pDummyInput->m_TargetX = m_InputData[g_Config.m_ClDummy].m_TargetX; pDummyInput->m_TargetY = m_InputData[g_Config.m_ClDummy].m_TargetY; pDummyInput->m_WantedWeapon = m_InputData[g_Config.m_ClDummy].m_WantedWeapon; pDummyInput->m_Fire += m_InputData[g_Config.m_ClDummy].m_Fire - m_LastData[g_Config.m_ClDummy].m_Fire; pDummyInput->m_NextWeapon += m_InputData[g_Config.m_ClDummy].m_NextWeapon - m_LastData[g_Config.m_ClDummy].m_NextWeapon; pDummyInput->m_PrevWeapon += m_InputData[g_Config.m_ClDummy].m_PrevWeapon - m_LastData[g_Config.m_ClDummy].m_PrevWeapon; m_InputData[!g_Config.m_ClDummy] = *pDummyInput; } if(g_Config.m_ClDummyControl) { CNetObj_PlayerInput *pDummyInput = &m_pClient->m_DummyInput; pDummyInput->m_Jump = g_Config.m_ClDummyJump; pDummyInput->m_Fire = g_Config.m_ClDummyFire; pDummyInput->m_Hook = g_Config.m_ClDummyHook; } // stress testing #ifdef CONF_DEBUG if(g_Config.m_DbgStress) { float t = Client()->LocalTime(); mem_zero(&m_InputData[g_Config.m_ClDummy], sizeof(m_InputData[0])); m_InputData[g_Config.m_ClDummy].m_Direction = ((int)t / 2) & 1; m_InputData[g_Config.m_ClDummy].m_Jump = ((int)t); m_InputData[g_Config.m_ClDummy].m_Fire = ((int)(t * 10)); m_InputData[g_Config.m_ClDummy].m_Hook = ((int)(t * 2)) & 1; m_InputData[g_Config.m_ClDummy].m_WantedWeapon = ((int)t) % NUM_WEAPONS; m_InputData[g_Config.m_ClDummy].m_TargetX = (int)(sinf(t * 3) * 100.0f); m_InputData[g_Config.m_ClDummy].m_TargetY = (int)(cosf(t * 3) * 100.0f); } #endif // check if we need to send input if(m_InputData[g_Config.m_ClDummy].m_Direction != m_LastData[g_Config.m_ClDummy].m_Direction) Send = true; else if(m_InputData[g_Config.m_ClDummy].m_Jump != m_LastData[g_Config.m_ClDummy].m_Jump) Send = true; else if(m_InputData[g_Config.m_ClDummy].m_Fire != m_LastData[g_Config.m_ClDummy].m_Fire) Send = true; else if(m_InputData[g_Config.m_ClDummy].m_Hook != m_LastData[g_Config.m_ClDummy].m_Hook) Send = true; else if(m_InputData[g_Config.m_ClDummy].m_WantedWeapon != m_LastData[g_Config.m_ClDummy].m_WantedWeapon) Send = true; else if(m_InputData[g_Config.m_ClDummy].m_NextWeapon != m_LastData[g_Config.m_ClDummy].m_NextWeapon) Send = true; else if(m_InputData[g_Config.m_ClDummy].m_PrevWeapon != m_LastData[g_Config.m_ClDummy].m_PrevWeapon) Send = true; // send at at least 10hz if(time_get() > LastSendTime + time_freq() / 25) Send = true; if(m_pClient->m_Snap.m_pLocalCharacter && m_pClient->m_Snap.m_pLocalCharacter->m_Weapon == WEAPON_NINJA && (m_InputData[g_Config.m_ClDummy].m_Direction || m_InputData[g_Config.m_ClDummy].m_Jump || m_InputData[g_Config.m_ClDummy].m_Hook)) Send = true; } // copy and return size m_LastData[g_Config.m_ClDummy] = m_InputData[g_Config.m_ClDummy]; if(!Send) return 0; LastSendTime = time_get(); mem_copy(pData, &m_InputData[g_Config.m_ClDummy], sizeof(m_InputData[0])); return sizeof(m_InputData[0]); } void CControls::OnRender() { enum { JOYSTICK_RUN_DISTANCE = 65536 / 8, GAMEPAD_DEAD_ZONE = 65536 / 8, }; int64 CurTime = time_get(); bool FireWasPressed = false; if(m_Joystick) { // Get input from left joystick int RunX = SDL_JoystickGetAxis(m_Joystick, LEFT_JOYSTICK_X); int RunY = SDL_JoystickGetAxis(m_Joystick, LEFT_JOYSTICK_Y); bool RunPressed = (RunX != 0 || RunY != 0); // Get input from right joystick int AimX = SDL_JoystickGetAxis(m_Joystick, SECOND_RIGHT_JOYSTICK_X); int AimY = SDL_JoystickGetAxis(m_Joystick, SECOND_RIGHT_JOYSTICK_Y); bool AimPressed = (AimX != 0 || AimY != 0); // Get input from another right joystick int HookX = SDL_JoystickGetAxis(m_Joystick, RIGHT_JOYSTICK_X); int HookY = SDL_JoystickGetAxis(m_Joystick, RIGHT_JOYSTICK_Y); bool HookPressed = (HookX != 0 || HookY != 0); if(m_JoystickRunPressed != RunPressed) { if(RunPressed) { if(m_JoystickTapTime + time_freq() > CurTime) // Tap in less than 1 second to jump m_InputData[g_Config.m_ClDummy].m_Jump = 1; } else m_InputData[g_Config.m_ClDummy].m_Jump = 0; m_JoystickTapTime = CurTime; } m_JoystickRunPressed = RunPressed; if(RunPressed) { m_InputDirectionLeft[g_Config.m_ClDummy] = (RunX < -JOYSTICK_RUN_DISTANCE); m_InputDirectionRight[g_Config.m_ClDummy] = (RunX > JOYSTICK_RUN_DISTANCE); } // Move 500ms in the same direction, to prevent speed bump when tapping if(!RunPressed && m_JoystickTapTime + time_freq() / 2 > CurTime) { m_InputDirectionLeft[g_Config.m_ClDummy] = 0; m_InputDirectionRight[g_Config.m_ClDummy] = 0; } if(HookPressed) { m_MousePos[g_Config.m_ClDummy] = vec2(HookX / 30, HookY / 30); ClampMousePos(); m_InputData[g_Config.m_ClDummy].m_Hook = 1; } else { m_InputData[g_Config.m_ClDummy].m_Hook = 0; } if(AimPressed) { m_MousePos[g_Config.m_ClDummy] = vec2(AimX / 30, AimY / 30); ClampMousePos(); } if(AimPressed != m_JoystickFirePressed) { // Fire when releasing joystick if(!AimPressed) { m_InputData[g_Config.m_ClDummy].m_Fire++; if((bool)(m_InputData[g_Config.m_ClDummy].m_Fire % 2) != AimPressed) m_InputData[g_Config.m_ClDummy].m_Fire++; FireWasPressed = true; } } m_JoystickFirePressed = AimPressed; } if(m_Gamepad) { // Get input from left joystick int RunX = SDL_JoystickGetAxis(m_Gamepad, LEFT_JOYSTICK_X); int RunY = SDL_JoystickGetAxis(m_Gamepad, LEFT_JOYSTICK_Y); if(m_UsingGamepad) { m_InputDirectionLeft[g_Config.m_ClDummy] = (RunX < -GAMEPAD_DEAD_ZONE); m_InputDirectionRight[g_Config.m_ClDummy] = (RunX > GAMEPAD_DEAD_ZONE); } // Get input from right joystick int AimX = SDL_JoystickGetAxis(m_Gamepad, RIGHT_JOYSTICK_X); int AimY = SDL_JoystickGetAxis(m_Gamepad, RIGHT_JOYSTICK_Y); if(abs(AimX) > GAMEPAD_DEAD_ZONE || abs(AimY) > GAMEPAD_DEAD_ZONE) { m_MousePos[g_Config.m_ClDummy] = vec2(AimX / 30, AimY / 30); ClampMousePos(); } if(!m_UsingGamepad && (abs(AimX) > GAMEPAD_DEAD_ZONE || abs(AimY) > GAMEPAD_DEAD_ZONE || abs(RunX) > GAMEPAD_DEAD_ZONE || abs(RunY) > GAMEPAD_DEAD_ZONE)) { m_UsingGamepad = true; } } if(g_Config.m_ClAutoswitchWeaponsOutOfAmmo && !GameClient()->m_GameInfo.m_UnlimitedAmmo && m_pClient->m_Snap.m_pLocalCharacter) { // 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 m_AmmoCount[m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS] = m_pClient->m_Snap.m_pLocalCharacter->m_AmmoCount; // Autoswitch weapon if we're out of ammo if((m_InputData[g_Config.m_ClDummy].m_Fire % 2 != 0 || FireWasPressed) && m_pClient->m_Snap.m_pLocalCharacter->m_AmmoCount == 0 && m_pClient->m_Snap.m_pLocalCharacter->m_Weapon != WEAPON_HAMMER && m_pClient->m_Snap.m_pLocalCharacter->m_Weapon != WEAPON_NINJA) { int w; for(w = WEAPON_LASER; w > WEAPON_GUN; w--) { if(w == m_pClient->m_Snap.m_pLocalCharacter->m_Weapon) continue; if(m_AmmoCount[w] > 0) break; } if(w != m_pClient->m_Snap.m_pLocalCharacter->m_Weapon) m_InputData[g_Config.m_ClDummy].m_WantedWeapon = w + 1; } } // update target pos if(m_pClient->m_Snap.m_pGameInfoObj && !m_pClient->m_Snap.m_SpecInfo.m_Active) m_TargetPos[g_Config.m_ClDummy] = m_pClient->m_LocalCharacterPos + m_MousePos[g_Config.m_ClDummy]; else if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_UsePosition) m_TargetPos[g_Config.m_ClDummy] = m_pClient->m_Snap.m_SpecInfo.m_Position + m_MousePos[g_Config.m_ClDummy]; else m_TargetPos[g_Config.m_ClDummy] = m_MousePos[g_Config.m_ClDummy]; } bool CControls::OnMouseMove(float x, float y) { if((m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED)) return false; if(g_Config.m_ClDyncam && g_Config.m_ClDyncamMousesens) { x = x * g_Config.m_ClDyncamMousesens / g_Config.m_InpMousesens; y = y * g_Config.m_ClDyncamMousesens / g_Config.m_InpMousesens; } if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID < 0) { x = x * m_pClient->m_pCamera->m_Zoom; y = y * m_pClient->m_pCamera->m_Zoom; } m_MousePos[g_Config.m_ClDummy] += vec2(x, y); // TODO: ugly ClampMousePos(); return true; } void CControls::ClampMousePos() { if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID < 0) { m_MousePos[g_Config.m_ClDummy].x = clamp(m_MousePos[g_Config.m_ClDummy].x, 200.0f, Collision()->GetWidth() * 32 - 200.0f); m_MousePos[g_Config.m_ClDummy].y = clamp(m_MousePos[g_Config.m_ClDummy].y, 200.0f, Collision()->GetHeight() * 32 - 200.0f); } else { float CameraMaxDistance = 200.0f; float FollowFactor = (g_Config.m_ClDyncam ? g_Config.m_ClDyncamFollowFactor : g_Config.m_ClMouseFollowfactor) / 100.0f; float DeadZone = g_Config.m_ClDyncam ? g_Config.m_ClDyncamDeadzone : g_Config.m_ClMouseDeadzone; float MaxDistance = g_Config.m_ClDyncam ? g_Config.m_ClDyncamMaxDistance : g_Config.m_ClMouseMaxDistance; float MouseMax = minimum((FollowFactor != 0 ? CameraMaxDistance / FollowFactor + DeadZone : MaxDistance), MaxDistance); float MinDistance = g_Config.m_ClDyncam ? g_Config.m_ClDyncamMinDistance : g_Config.m_ClMouseMinDistance; float MouseMin = MinDistance; float MDistance = length(m_MousePos[g_Config.m_ClDummy]); if(MDistance < 0.001f) { m_MousePos[g_Config.m_ClDummy].x = 0.001f; m_MousePos[g_Config.m_ClDummy].y = 0; MDistance = 0.001f; } if(MDistance < MouseMin) m_MousePos[g_Config.m_ClDummy] = normalize_pre_length(m_MousePos[g_Config.m_ClDummy], MDistance) * MouseMin; MDistance = length(m_MousePos[g_Config.m_ClDummy]); if(MDistance > MouseMax) m_MousePos[g_Config.m_ClDummy] = normalize_pre_length(m_MousePos[g_Config.m_ClDummy], MDistance) * MouseMax; } }