-
-
Save ThangCZ/52ebf7cbea9f18549ac6854688295386 to your computer and use it in GitHub Desktop.
Helicam
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fovMax = 80.0; | |
const fovMin = 10.0; // max zoom level (smaller fov is more zoom) | |
const zoomspeed = 2.0; // camera zoom speed | |
const speedLr = 3.0; // speed by which the camera pans left-right | |
const speedUd = 3.0; // speed by which the camera pans up-down | |
// control id to toggle nightvision mode. Default: INPUT_AIM (Right mouse btn) | |
const toggleNightvision = 25; | |
// control id to lock onto a vehicle with the camera. Default (spacebar) | |
const toggleLockOn = 22; | |
helicam = false; | |
// default FOV when the helicam is enabled. | |
const fov = (fovMax + fovMin) * 0.5; | |
let nightvision = false; | |
let cam; | |
let scaleform; | |
let lockedOnVehicle = null; | |
let lastLockedOn = 0; | |
const spotlightButton = 101; // Numpad 5 | |
const spotlightSyncRate = 5; | |
const serverSyncRate = 5; | |
let lerpValue = 0; | |
let spotlightTimer = null; | |
let spotlightRemoteId = null; | |
let spotlights = {}; | |
let spotlightBrightness = 5; | |
let spotlightRoundness = 20; | |
let spotlightRadius = 4; | |
let spotlightFalloff = 2; | |
mp.events.add('switchhelicam', () => { | |
if (helicam) { | |
destroyHelicam(); | |
} else { | |
enableHelicam(); | |
} | |
}); | |
async function enableHelicam() { | |
mp.game.graphics.setTimecycleModifier('heliGunCam'); | |
mp.game.graphics.setTimecycleModifierStrength(0.3); | |
scaleform = mp.game.graphics.requestScaleformMovie('HELI_CAM'); | |
while (!mp.game.graphics.hasScaleformMovieLoaded(scaleform)) { | |
await mp.game.waitAsync(0); | |
} | |
const lPed = mp.players.local; | |
const heli = lPed.vehicle; | |
cam = mp.cameras.new( | |
'DEFAULT_SCRIPTED_FLY_CAMERA', | |
lPed.position, | |
new mp.Vector3(0, 0, mp.players.local.getHeading()), | |
60, | |
); | |
cam.setActive(true); | |
cam.setRot(0.0, 0.0, heli.getHeading(), 2); | |
cam.setFov(fov); | |
cam.setFarClip(5000); | |
mp.game.cam.renderScriptCams(true, false, 0, true, false); | |
// _ATTACH_CAM_TO_VEHICLE_BONE | |
mp.game.invoke('0x8DB3F12A02CAEF72', cam.handle, heli.handle, | |
getSpotlightBoneIndex(heli), false, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5, false); | |
mp.game.graphics.pushScaleformMovieFunction(scaleform, 'SET_CAM_LOGO'); | |
mp.game.graphics.pushScaleformMovieFunctionParameterInt(0); | |
mp.game.graphics.popScaleformMovieFunctionVoid(); | |
helicam = true; | |
} | |
function destroyHelicam() { | |
// CLEAR_TIMECYCLE_MODIFIER | |
mp.game.invoke('0x0F07E7745A236711'); | |
// CLEAR_FOCUS | |
mp.game.invoke('0x31B73D1EA9F01DA2'); | |
mp.game.cam.renderScriptCams(false, false, 0, true, false); | |
if (scaleform != null || scaleform != 0) { | |
mp.game.graphics.setScaleformMovieAsNoLongerNeeded(scaleform); | |
} | |
if (cam != null) { | |
cam.destroy(true); | |
cam = null; | |
} | |
helicam = false; | |
mp.game.graphics.setSeethrough(false); | |
mp.game.graphics.setNightvision(false); | |
nightvision = false; | |
lockedOnVehicle = null; | |
destroySpotlight(); | |
} | |
function handleHelicamZoom() { | |
mp.game.controls.disableAllControlActions(2); | |
const x = mp.game.controls.getDisabledControlNormal(7, 1) * speedLr; | |
const y = mp.game.controls.getDisabledControlNormal(7, 2) * speedUd; | |
const zoomIn = mp.game.controls.getDisabledControlNormal(2, 40) * zoomspeed; | |
const zoomOut = | |
mp.game.controls.getDisabledControlNormal(2, 41) * zoomspeed; | |
let currentRot = cam.getRot(2); | |
currentRot = new mp.Vector3(currentRot.x - y, 0, currentRot.z - x); | |
cam.setRot(currentRot.x, currentRot.y, currentRot.z, 2); | |
if (zoomIn > 0) { | |
let currentFov = cam.getFov(); | |
currentFov -= zoomIn; | |
if (currentFov < fovMin) currentFov = fovMin; | |
cam.setFov(currentFov); | |
} else if (zoomOut > 0) { | |
let currentFov = cam.getFov(); | |
currentFov += zoomOut; | |
if (currentFov > fovMax) currentFov = fovMax; | |
cam.setFov(currentFov); | |
} | |
} | |
function drawAltFovScaleform() { | |
mp.game.graphics.pushScaleformMovieFunction( | |
scaleform, | |
'SET_ALT_FOV_HEADING', | |
); | |
mp.game.graphics.pushScaleformMovieFunctionParameterFloat( | |
mp.players.local.vehicle.position.z, | |
); | |
const zoomValue = (1.0/(fovMax-fovMin))*(cam.getFov()-fovMin); | |
mp.game.graphics.pushScaleformMovieFunctionParameterFloat(zoomValue); | |
mp.game.graphics.pushScaleformMovieFunctionParameterFloat(cam.getRot(2).z); | |
mp.game.graphics.popScaleformMovieFunctionVoid(); | |
mp.game.graphics.drawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, | |
true); | |
} | |
mp.events.add('render', () => { | |
if (!helicam) { | |
return; | |
} | |
if (!mp.players.local.vehicle || !mp.players.local.vehicle.doesExist()) { | |
destroyHelicam(); | |
return; | |
} | |
if (cam !== null && cam.isActive() && cam.isRendering()) { | |
handleHelicamZoom(); | |
} | |
if (mp.game.controls.isDisabledControlJustPressed(0, toggleNightvision)) { | |
changeVision(); | |
} | |
if (lockedOnVehicle) { | |
if (lockedOnVehicle.handle != 0 && | |
(isLockOnVisible(cam, lockedOnVehicle) || | |
Date.now() - lastLockedOn < 1000 ) && | |
!mp.game.controls.isDisabledControlJustPressed(0, toggleLockOn)) { | |
cam.pointAt(lockedOnVehicle.handle, 0, 0, 0, true); | |
renderVehicleInfo(lockedOnVehicle); | |
} else { | |
mp.game.audio.playSoundFrontend( | |
-1, 'SELECT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', false); | |
lockedOnVehicle = null; | |
cam.stopPointing(); | |
} | |
} else if (mp.game.controls.isDisabledControlJustPressed(0, toggleLockOn)) { | |
const vehicleDetected = pointingAtVehicle(cam); | |
if (vehicleDetected != null && vehicleDetected.handle != 0) { | |
mp.game.audio.playSoundFrontend( | |
-1, 'SELECT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', false); | |
lockedOnVehicle = vehicleDetected; | |
lastLockedOn = Date.now(); | |
} | |
} | |
drawAltFovScaleform(); | |
}); | |
function changeVision() { | |
mp.game.audio.playSoundFrontend( | |
-1, 'SELECT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', false); | |
nightvision = !nightvision; | |
mp.game.graphics.setNightvision(nightvision); | |
} | |
function renderVehicleInfo(vehicle) { | |
const vehname = mp.game.ui.getLabelText( | |
mp.game.vehicle.getDisplayNameFromVehicleModel(vehicle.model), | |
); | |
const licenseplate = vehicle.getNumberPlateText(); | |
const speed = (vehicle.getSpeed() * 3.6) | 0; | |
mp.game.graphics.drawText( | |
`Model: ${vehname}\n Plate: ${licenseplate}\n Speed: ${speed} km/h`, | |
[0.5, 0.9], | |
{ | |
font: 0, | |
color: [255, 255, 255, 185], | |
scale: [0.0, 0.45], | |
outline: true, | |
}, | |
); | |
} | |
function getPointinigAtEntity(camera, distance = 1000) { | |
const position = camera.getCoord(); | |
const direction = camera.getDirection(); | |
const farAway = new mp.Vector3( | |
direction.x * distance + position.x, | |
direction.y * distance + position.y, | |
direction.z * distance + position.z, | |
); | |
const result = mp.raycasting.testPointToPoint(position, farAway, | |
mp.players.local.vehicle.handle, 19); | |
if (result && result.entity.handle !== mp.players.local.handle && | |
result.entity.handle !== mp.players.local.vehicle.handle) { | |
return result.entity; | |
} | |
return null; | |
} | |
function pointingAtVehicle(camera, distance = 1000) { | |
const entity = getPointinigAtEntity(camera, distance); | |
if (entity && entity.type === 'vehicle') { | |
return entity; | |
} | |
return null; | |
} | |
// TODO: The whole lock on thing is a bit fiddly and needs some love. | |
function isLockOnVisible(camera, entity, distance = 1000) { | |
return true; | |
const remoteEntity = getPointinigAtEntity(camera, distance); | |
if (remoteEntity === entity) { | |
lastLockedOn = Date.now(); | |
return true; | |
} | |
return false; | |
} | |
function destroySpotlight() { | |
if (spotlightTimer) { | |
clearInterval(spotlightTimer); | |
spotlightTimer = null; | |
mp.events.callRemote('deletespotlight', spotlightRemoteId); | |
} | |
} | |
function syncSpotlight() { | |
const direction = cam.getDirection(); | |
mp.events.callRemote('updatespotlight', spotlightRemoteId, | |
direction.x, direction.y, direction.z, | |
lockedOnVehicle ? lockedOnVehicle.remoteId : -1); | |
} | |
mp.events.add('render', () => { | |
// float TIMESTEP(); | |
const timeDelta = mp.game.invokeFloat('0x0000000050597EE2'); | |
lerpValue += timeDelta / (1.0 / serverSyncRate); | |
for (const [sHelicopter, direction] of Object.entries(spotlights)) { | |
const helicopter = mp.vehicles.atRemoteId(sHelicopter); | |
if (!helicopter || !helicopter.doesExist()) { | |
continue; | |
} | |
const matrix = helicopter.getMatrix(null, null, null, null); | |
const spotlightPos = helicopter.getWorldPositionOfBone( | |
getSpotlightBoneIndex(helicopter)); | |
let pos = new mp.Vector3(spotlightPos.x, spotlightPos.y, spotlightPos.z); | |
pos = pos.subtract(matrix.upVector.multiply(0.5)); | |
let drawPos = new mp.Vector3(direction.x, direction.y, direction.z); | |
if (direction.vehicle != -1) { | |
const lockedVehicle = mp.vehicles.atRemoteId(direction.vehicle); | |
if (lockedVehicle && lockedVehicle.doesExist()) { | |
drawPos = lockedVehicle.position.subtract(pos); | |
} | |
} else { | |
drawPos = lerpVec(direction.oldX, direction.oldY, direction.oldZ, | |
direction.x, direction.y, direction.z, lerpValue); | |
} | |
mp.game.graphics.drawSpotLightWithShadow(pos.x, pos.y, pos.z, | |
drawPos.x, drawPos.y, drawPos.z, 255, 255, 255, 1000, | |
spotlightBrightness, spotlightRoundness, | |
spotlightRadius, spotlightFalloff, parseInt(sHelicopter) + 1); | |
} | |
}); | |
mp.events.add('updatespotlights', (spotlightsJson) => { | |
const newSpotlights = {}; | |
for (const [sHelicopter, direction] of Object.entries(JSON.parse( | |
spotlightsJson))) { | |
direction.oldX = direction.x; | |
direction.oldY = direction.y; | |
direction.oldZ = direction.z; | |
if (sHelicopter in spotlights && direction.vehicle == -1 && | |
spotlights[sHelicopter].vehicle == -1) { | |
const lerpedDirection = lerpVec(spotlights[sHelicopter].oldX, | |
spotlights[sHelicopter].oldY, spotlights[sHelicopter].oldZ, | |
spotlights[sHelicopter].x, spotlights[sHelicopter].y, | |
spotlights[sHelicopter].z, lerpValue); | |
direction.oldX = lerpedDirection.x; | |
direction.oldY = lerpedDirection.y; | |
direction.oldZ = lerpedDirection.z; | |
} | |
newSpotlights[sHelicopter] = direction; | |
} | |
spotlights = newSpotlights; | |
lerpValue = 0; | |
}); | |
mp.keys.bind(spotlightButton, false, () => { | |
if (!helicam) { | |
return; | |
} | |
if (spotlightTimer) { | |
destroySpotlight(); | |
return; | |
} | |
spotlightRemoteId = mp.players.local.vehicle.remoteId; | |
spotlightTimer = setInterval(syncSpotlight, 1000 / spotlightSyncRate); | |
}); | |
function getSpotlightBoneIndex(vehicle) { | |
for (const bone of ['searchlight_base', 'turret_1base']) { | |
const boneIndex = vehicle.getBoneIndexByName(bone); | |
if (boneIndex !== -1) { | |
return boneIndex; | |
} | |
} | |
return -1; | |
} | |
function lerpVec(oldX, oldY, oldZ, newX, newY, newZ, t) { | |
return new mp.Vector3( | |
oldX + (newX - oldX) * t, | |
oldY + (newY - oldY) * t, | |
oldZ + (newZ - oldZ) * t, | |
); | |
} | |
// Debug stuff | |
// Bind to H | |
mp.keys.bind(0x48, true, function() { | |
if (mp.players.local.vehicle && mp.players.local.vehicle.doesExist()&& | |
mp.players.local.vehicle.getClass() == 15 && | |
getSpotlightBoneIndex(mp.players.local.vehicle) !== -1) { | |
mp.events.call('switchhelicam'); | |
} | |
}); | |
// Change properties of the spotlight rendering | |
mp.events.add('playerCommand', (command) => { | |
const args = command.split(/[ ]+/); | |
const commandName = args[0]; | |
args.shift(); | |
if (commandName == 'brightness') { | |
spotlightBrightness = Number(args[0]); | |
} | |
if (commandName == 'roundness') { | |
spotlightRoundness = Number(args[0]); | |
} | |
if (commandName == 'radius') { | |
spotlightRadius = Number(args[0]); | |
} | |
if (commandName == 'falloff') { | |
spotlightFalloff = Number(args[0]); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const syncRate = 5; | |
const spotlights = {}; | |
mp.events.add('updatespotlight', (_, remoteId, dirx, diry, dirz, veh) => { | |
spotlights[remoteId] = {x: dirx, y: diry, z: dirz, vehicle: veh}; | |
}); | |
mp.events.add('deletespotlight', (_, remoteId) => { | |
delete spotlights[remoteId]; | |
}); | |
setInterval(() => { | |
const result = JSON.stringify(spotlights); | |
mp.players.call('updatespotlights', [result]); | |
}, 1000 / syncRate); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment