// =========================================================================== // Asshat-Gaming Roleplay // https://github.com/VortrexFTW/gtac_asshat_rp // Copyright (c) 2020 Asshat-Gaming (https://asshatgaming.com) // --------------------------------------------------------------------------- // FILE: mouse-camera.js // DESC: Provides a freelook camera similar to SA for III and VC // TYPE: Client (JavaScript) // =========================================================================== // CREDITS TO LUCASC190 FOR MAKING THE MOUSE CAMERA // WALKING CODE ADDED BY VORTREX function SetStandardControlsEnabled(bEnabled) { if (typeof gta == "undefined") { return false; } if (game.standardControls === undefined) { logToConsole(LOG_WARN, "game.standardControls not implemented"); return; } game.standardControls = bEnabled; } function GetCurrentPlayerIndex() { return 0; } function GetPlayerPed(uiIndex) { if (uiIndex >= 1) throw new Error("player index out of range"); return localPlayer; } function GetPedVehicle(pPed) { return pPed.vehicle; } let ENTITYTYPE_BUILDING = 1; let ENTITYTYPE_VEHICLE = 2; let ENTITYTYPE_PED = 3; let ENTITYTYPE_OBJECT = 4; let ENTITYTYPE_DUMMY = 5; function GetEntityType(Entity) { if (Entity.isType(ELEMENT_BUILDING)) return ENTITYTYPE_BUILDING; if (Entity.isType(ELEMENT_VEHICLE)) return ENTITYTYPE_VEHICLE; if (Entity.isType(ELEMENT_PED)) return ENTITYTYPE_PED; if (Entity.isType(ELEMENT_OBJECT)) return ENTITYTYPE_OBJECT; //if (Entity.isType(ELEMENT_DUMMY)) // return ENTITYTYPE_DUMMY; return undefined; } function GetPlaceableMatrix(pPlaceable) { if (pPlaceable == GetCamera()) return game.cameraMatrix; return pPlaceable.matrix; } function GetEntityModel(pEntity) { return pEntity; } function GetModelBoundingSphere(usModel) { return [usModel.boundingRadius, usModel.boundingCentre.x, usModel.boundingCentre.y, usModel.boundingCentre.z]; } function GetMouseSpeed() { if (gui.cursorEnabled) return [0, 0]; let MouseSpeed = game.getMouseSpeed(); return [MouseSpeed.x, -MouseSpeed.y]; } function GetMouseSensitivity() { if (game.getMouseSensitivity === undefined) { //logToConsole(LOG_ERROR, "game.getMouseSensitivity not implemented"); return [0.0025, 0.003]; } let MouseSensitivity = game.getMouseSensitivity(); return [MouseSensitivity.x, MouseSensitivity.y]; } let GetCamera; { const Camera = Symbol(); GetCamera = function () { return Camera; } } function AreEntityCollisionsEnabled(pEntity) { return pEntity.collisionsEnabled; } function SetEntityCollisionsEnabled(pEntity, bCollisionsEnabled) { pEntity.collisionsEnabled = bCollisionsEnabled; } function ProcessLineOfSight(vecStartX, vecStartY, vecStartZ, vecEndX, vecEndY, vecEndZ, bCheckBuildings, bCheckVehicles, bCheckPeds, bCheckObjects, bCheckDummies, bCheckSeeThroughStuff, bIgnoreSomeObjectsForCamera) { if (game.processLineOfSight === undefined) { logToConsole(LOG_WARN, "game.processLineOfSight not implemented"); return [null]; } let Result = game.processLineOfSight([vecStartX, vecStartY, vecStartZ], [vecEndX, vecEndY, vecEndZ], bCheckBuildings, bCheckVehicles, bCheckPeds, bCheckObjects, bCheckDummies, bCheckSeeThroughStuff, bIgnoreSomeObjectsForCamera); if (Result == null) return [null]; return [Result.position.x, Result.position.y, Result.position.z, Result.normal.x, Result.normal.y, Result.normal.z, Result.entity]; } function SetPlaceableMatrix(pPlaceable, mat) { if (pPlaceable == GetCamera()) { game.setCameraMatrix(mat); return; } pPlaceable.matrix = mat; } const UpdateCamera = game.updateCamera; let GetTickCount; { let FrameCount = 0; setInterval(() => { ++FrameCount; }, 0); let GTAFrameCount = 0; addEventHandler("OnProcess", (event, deltaTime) => { ++GTAFrameCount; }); GetTickCount = function (bGTA, bFrames) { if (bFrames) return bGTA ? GTAFrameCount : FrameCount; else return bGTA ? game.tickCount : sdl.ticks; } } function easingSinusoidalInOut(t, b, c, d)//TODO: Move this to MathUtil.js { return -c / 2 * (Math.cos((Math.PI) * t / d) - 1) + b; } //TODO: extract function applyMultiplierTimeStep(m, t)//TODO: Move this to MathUtil.js { return Math.max(Math.min(1.0 - (1.0 - m) * (t), 1), 0); } //TODO: getOffset //TODO: round //TODO: getNumberBetween //TODO: split //TODO: isWhiteSpaceCharacter //TODO: isControlCharacter //TODO: alert //TODO: confirm const identityMatrix = new Matrix4x4(); if (identityMatrix.setIdentity === undefined) { identityMatrix.m11 = 1; identityMatrix.m12 = 0; identityMatrix.m13 = 0; identityMatrix.m14 = 0; identityMatrix.m21 = 0; identityMatrix.m22 = 1; identityMatrix.m23 = 0; identityMatrix.m24 = 0; identityMatrix.m31 = 0; identityMatrix.m32 = 0; identityMatrix.m33 = 1; identityMatrix.m34 = 0; identityMatrix.m41 = 0; identityMatrix.m42 = 0; identityMatrix.m43 = 0; identityMatrix.m44 = 1; } const cameraIdentityMatrix = new Matrix4x4(); cameraIdentityMatrix.m11 = -1; cameraIdentityMatrix.m12 = 0; cameraIdentityMatrix.m13 = 0; cameraIdentityMatrix.m14 = 0; cameraIdentityMatrix.m21 = 0; cameraIdentityMatrix.m22 = 1; cameraIdentityMatrix.m23 = 0; cameraIdentityMatrix.m24 = 0; cameraIdentityMatrix.m31 = 0; cameraIdentityMatrix.m32 = 0; cameraIdentityMatrix.m33 = 1; cameraIdentityMatrix.m34 = 0; cameraIdentityMatrix.m41 = 0; cameraIdentityMatrix.m42 = 0; cameraIdentityMatrix.m43 = 0; cameraIdentityMatrix.m44 = 1; function createMultipliedMatrix() { let matrix = new Matrix4x4(); matrix.setMultiply.apply(matrix, arguments); return matrix; } function createXRotationMatrix(x) { let matrix = new Matrix4x4(); matrix.setRotateX(x); return matrix; } function createYRotationMatrix(x) { let matrix = new Matrix4x4(); matrix.setRotateY(x); return matrix; } function createZRotationMatrix(z) { let matrix = new Matrix4x4(); matrix.setRotateZ(z); return matrix; } function createTranslationMatrix(x, y, z) { let matrix = new Matrix4x4(); matrix.setTranslate([x, y, z]); return matrix; } //TODO: createScaleMatrix function getDotProduct(x, y, z, x2, y2, z2) { return x * x2 + y * y2 + z * z2; } function getCrossProduct(x, y, z, x2, y2, z2) { return [y * z2 - z * y2, z * x2 - x * z2, x * y2 - y * x2]; } function getLength(x, y, z) { return Math.sqrt(getDotProduct(x, y, z, x, y, z)); } function normalise(x, y, z) { let length = getLength(x, y, z); if (length == 0) throw new Error("an attempt was made to normalise a three dimensional vector with a length of zero"); return [x / length, y / length, z / length]; } function createLookAtLHMatrix(eyeX, eyeY, eyeZ, atX, atY, atZ, upX, upY, upZ) { let matrix = new Matrix4x4(); let [lookX, lookY, lookZ] = normalise(atX - eyeX, atY - eyeY, atZ - eyeZ); let [rightX, rightY, rightZ] = normalise.apply(null, getCrossProduct(upX, upY, upZ, lookX, lookY, lookZ)); [upX, upY, upZ] = getCrossProduct(lookX, lookY, lookZ, rightX, rightY, rightZ); matrix.m11 = rightX; matrix.m12 = rightY; matrix.m13 = rightZ; matrix.m14 = 0; matrix.m21 = lookX; matrix.m22 = lookY; matrix.m23 = lookZ; matrix.m24 = 0; matrix.m31 = upX; matrix.m32 = upY; matrix.m33 = upZ; matrix.m34 = 0; matrix.m41 = eyeX; matrix.m42 = eyeY; matrix.m43 = eyeZ; matrix.m44 = 1; matrix.m41 = eyeX; matrix.m42 = eyeY; matrix.m43 = eyeZ; matrix.m44 = 1; return matrix; } function getDifferenceBetweenAngles(current, target) { let f = (((target - current) + Math.PI) / (Math.PI * 2)); return ((f - Math.floor(f)) * (Math.PI * 2)) - Math.PI; } let easeCamera = false; let easeStartTicks; let easeDuration; let easeStartPosX, easeStartPosY, easeStartPosZ; let easeStartLookX, easeStartLookY, easeStartLookZ; let easeStartUpX, easeStartUpY, easeStartUpZ; function getCameraPositionInfo(matrix) { return [matrix.m41, matrix.m42, matrix.m43, matrix.m21, matrix.m22, matrix.m23, matrix.m31, matrix.m32, matrix.m33]; } function startCameraEase() { easeCamera = true; easeStartTicks = GetTickCount(true, false); easeDuration = 1000; let matrix = GetPlaceableMatrix(GetCamera()); [easeStartPosX, easeStartPosY, easeStartPosZ, easeStartLookX, easeStartLookY, easeStartLookZ, easeStartUpX, easeStartUpY, easeStartUpZ] = getCameraPositionInfo(matrix); } function applyCameraEase(matrix) { if (!easeCamera) return matrix; let ease = (GetTickCount(true, false) - easeStartTicks) / easeDuration; if (ease < 1) { ease = easingSinusoidalInOut(ease, 0, 1, 1); let [newPosX, newPosY, newPosZ, newLookX, newLookY, newLookZ, newUpX, newUpY, newUpZ] = getCameraPositionInfo(matrix); let easePosX = easeStartPosX + (newPosX - easeStartPosX) * ease; let easePosY = easeStartPosY + (newPosY - easeStartPosY) * ease; let easePosZ = easeStartPosZ + (newPosZ - easeStartPosZ) * ease; let easeLookX = easeStartLookX + (newLookX - easeStartLookX) * ease; let easeLookY = easeStartLookY + (newLookY - easeStartLookY) * ease; let easeLookZ = easeStartLookZ + (newLookZ - easeStartLookZ) * ease; let easeUpX = easeStartUpX + (newUpX - easeStartUpX) * ease; let easeUpY = easeStartUpY + (newUpY - easeStartUpY) * ease; let easeUpZ = easeStartUpZ + (newUpZ - easeStartUpZ) * ease; return createLookAtLHMatrix(easePosX, easePosY, easePosZ, easePosX + easeLookX, easePosY + easeLookY, easePosZ + easeLookZ, easeUpX, easeUpY, easeUpZ); } return matrix; } function isCameraEasing() { return easeCamera && GetTickCount(true, false) < (easeStartTicks + easeDuration); } let oldCameraTarget = null; let OldPosition = null;//2019 Lucas was here! let cameraRotZ; let cameraRotY; function getCameraTarget() { let playerPed = GetPlayerPed(GetCurrentPlayerIndex()); let vehicle = GetPedVehicle(playerPed); if (vehicle != null) return vehicle; if (playerPed != null) { //if (playerPed.health <= 1)//Breaks because of fade//2019 Lucas was here! // return null; return playerPed; } return null; } function isRelativeToTarget(target) { if (GetEntityType(target) == ENTITYTYPE_PED) return false; return false } function isClipped(target) { if (GetEntityType(target) == ENTITYTYPE_PED) return true; return true; } //2019 Lucas was here! function ShouldReturnToRestRotation(Target) { if (GetEntityType(Target) == ENTITYTYPE_PED) return false; return true; } function getCameraRestRotation(target) { let targetMatrix = GetPlaceableMatrix(target); let rotZ; if (isRelativeToTarget(target)) rotZ = 0; else rotZ = -Math.atan2(targetMatrix.m21, targetMatrix.m22); let rotY = -0.2; return [rotZ, rotY]; } function resetCameraRotation() { [cameraRotZ, cameraRotY] = getCameraRestRotation(getCameraTarget()); } //2019 Lucas was here! let DeltaTime = 0; addEventHandler("OnProcess", (event, deltaTime) => { DeltaTime = deltaTime; if (!localPlayer) { return false; } }); let IdleTime = 0;//2019 Lucas was here! function processReturnToRestRotation() { //resetCameraRotation();//2019 Lucas was here! //2019 Lucas was here! let Target = getCameraTarget(); if (!ShouldReturnToRestRotation(Target)) return; IdleTime += DeltaTime; if (IdleTime > 1.5) { let Velocity = Target.velocity; let Matrix = Target.matrix; let Speed = getDotProduct(Velocity.x, Velocity.y, Velocity.z, Matrix.getElement(1 * 4 + 0), Matrix.getElement(1 * 4 + 1), Matrix.getElement(1 * 4 + 2)); let AbsSpeed = Math.abs(Speed); let Multiplier = Math.min(AbsSpeed / 0.75, 1); if (Multiplier != 0) { let [TargetCameraRotZ2, TargetCameraRotY2] = getCameraRestRotation(Target); if (Speed < 0) TargetCameraRotZ2 += Math.PI; let TimeStep = game.timeStep / 50 * 60; cameraRotZ += getDifferenceBetweenAngles(cameraRotZ, TargetCameraRotZ2) * applyMultiplierTimeStep(1 / 20, TimeStep) * Multiplier; cameraRotY += getDifferenceBetweenAngles(cameraRotY, TargetCameraRotY2) * applyMultiplierTimeStep(1 / 20, TimeStep) * Multiplier; } } } function cancelReturnToRestRotation() { IdleTime = 0;//2019 Lucas was here! } let distance; let zIncrease; function getCameraOffsetInfo(target) { if (GetEntityType(target) == ENTITYTYPE_PED) { let distance = 4; let zIncrease = 0.8; let offsetX = 0; let offsetY = 0; let offsetZ = 0; return [distance, zIncrease, offsetX, offsetY, offsetZ]; } let model = GetEntityModel(target); let [radius] = GetModelBoundingSphere(model); let minDistance; let maxDistance; let minZIncrease; let maxZIncrease; let minRadius; let maxRadius; let offsetX; let offsetY; let offsetZ; if (radius <= 3.0535011291504) { minDistance = 4; maxDistance = 8; minZIncrease = 0.5; maxZIncrease = 1; minRadius = 2; maxRadius = 3.0535011291504; } else { minDistance = 8; maxDistance = 16; minZIncrease = 1; maxZIncrease = 2; minRadius = 3.05350112915042; maxRadius = 6.3955960273743; } offsetX = 0; offsetY = 0; offsetZ = 0; distance = minDistance + (radius - minRadius) / (maxRadius - minRadius) * (maxDistance - minDistance); zIncrease = minZIncrease + (radius - minRadius) / (maxRadius - minRadius) * (maxZIncrease - minZIncrease); return [distance, zIncrease, offsetX, offsetY, offsetZ]; } function update() { let target = getCameraTarget(); if (target != null) { if (oldCameraTarget != target) { //if (oldCameraTarget != null)//2019 Lucas was here! let Position = target.position; if (OldPosition == null || getLength(Position.x - OldPosition.x, Position.y - OldPosition.y, Position.z - OldPosition.z) < 10) startCameraEase() resetCameraRotation() } let [mouseSpeedX, mouseSpeedY] = GetMouseSpeed(); let [mouseSensitivityX, mouseSensitivityY] = GetMouseSensitivity(); mouseSpeedX = mouseSpeedX * mouseSensitivityX * 2; mouseSpeedY = mouseSpeedY * mouseSensitivityY * 2; if (mouseSpeedX == 0 && mouseSpeedY == 0) { processReturnToRestRotation(); } else { cameraRotZ = cameraRotZ - mouseSpeedX; cameraRotY = cameraRotY - mouseSpeedY; cancelReturnToRestRotation(); } cameraRotY = Math.max(cameraRotY, -Math.PI / 2 + 0.01); if (GetEntityType(target) != ENTITYTYPE_PED) cameraRotY = Math.min(cameraRotY, Math.PI / 8.5);//2019 Lucas was here! else cameraRotY = Math.min(cameraRotY, Math.PI / 4); let camera = GetCamera(); let targetMatrix = GetPlaceableMatrix(target); let [distance, zIncrease, offsetX, offsetY, offsetZ] = getCameraOffsetInfo(target); let offsetTranslationMatrix = createTranslationMatrix(offsetX, offsetY, offsetZ); targetMatrix = createMultipliedMatrix(offsetTranslationMatrix, targetMatrix); let targetPosX, targetPosY, targetPosZ; if (isRelativeToTarget(target)) [targetPosX, targetPosY, targetPosZ] = [0, 0, 0]; else [targetPosX, targetPosY, targetPosZ] = [targetMatrix.m41, targetMatrix.m42, targetMatrix.m43]; let distanceTranslationMatrix = createTranslationMatrix(0, -distance, 0); targetPosZ = targetPosZ + zIncrease; let targetTranslationMatrix = createTranslationMatrix(targetPosX, targetPosY, targetPosZ); let offsetRotationX = createXRotationMatrix(cameraRotY); let offsetRotationZ = createZRotationMatrix(cameraRotZ); let cameraMatrix = createMultipliedMatrix(cameraIdentityMatrix, distanceTranslationMatrix, offsetRotationX, offsetRotationZ, targetTranslationMatrix); if (isRelativeToTarget(target)) { cameraMatrix = createMultipliedMatrix(cameraMatrix, targetMatrix); targetTranslationMatrix = createMultipliedMatrix(targetTranslationMatrix, targetMatrix); } if (isClipped(target)) { let startX = targetTranslationMatrix.m41; let startY = targetTranslationMatrix.m42; let startZ = targetTranslationMatrix.m43; let endX = cameraMatrix.m41; let endY = cameraMatrix.m42; let endZ = cameraMatrix.m43; let checkBuildings = true; let checkVehicles = true; let checkPeds = true; let checkObjects = true; let checkDummies = false; let checkSeeThroughStuff = false; let ignoreSomeObjectsForCamera = true; let collisionsEnabled = AreEntityCollisionsEnabled(target); if (collisionsEnabled) SetEntityCollisionsEnabled(target, false); let [positionX, positionY, positionZ, normalX, normalY, normalZ, targetEntity] = ProcessLineOfSight(startX, startY, startZ, endX, endY, endZ, checkBuildings, checkVehicles, checkPeds, checkObjects, checkDummies, checkSeeThroughStuff, ignoreSomeObjectsForCamera); if (collisionsEnabled) SetEntityCollisionsEnabled(target, true); if (positionX != null) { //2019 Lucas was here! let Distance = 0.3; positionX += normalX * Distance; positionY += normalY * Distance; positionZ += normalZ * Distance; cameraMatrix.m41 = positionX; cameraMatrix.m42 = positionY; cameraMatrix.m43 = positionZ; } } if (isCameraEasing()) cameraMatrix = applyCameraEase(cameraMatrix); SetPlaceableMatrix(camera, cameraMatrix); UpdateCamera(camera); } oldCameraTarget = target; OldPosition = (target != null) ? target.position : null;//2019 Lucas was here! return target != null; } addEventHandler("OnCameraProcess", (event) => { if (mouseCameraEnabled) { update(); event.preventDefault(); } }); function toggleMouseCamera() { mouseCameraEnabled = !mouseCameraEnabled; }