-- DESCRIPTION: Grappling Hook local U = require "scriptbank\\utillib" local Q = require "scriptbank\\quatlib" local P = require "scriptbank\\physlib" local rad = math.rad local deg = math.deg local max = math.max local min = math.min local floor = math.floor local grappleHooks = {} local epressed = false local throw_speed = 10 local rotSpeed = 15 local moveSpeed = 0.04 local returnSpeed = 0.06 local hold_height = -5 local hold_dist = 50 local max_throw_dist = 3 --maximum time (in seconds) the hook will travel if it doesnt hit anything local max_charge_time = 1 --seconds til max throw length reached local show_timer = false local ghtimer = 0 local perc = 0 local n_max_time = 0 local tppEnt = GetGamePlayerControlThirdpersonCharactere() function grapplinghook3_init_name( e, name ) Include( "utillib.lua" ) Include( "quatlib.lua" ) grappleHooks[ e ] = { name = name, state = "wait pick up" } max_charge_time = max_charge_time * 1000 max_throw_dist = max_throw_dist * 1000 end local function round( num, idp ) local mult = 10^( idp or 0 ) return floor( num * mult + 0.5 ) / mult end local function getPlayerEyePos() -- correct player y pos for crouching if GetGamePlayerStatePlayerDucking() == 0 then return g_PlayerPosX, g_PlayerPosY + 31 + hold_height, g_PlayerPosZ else return g_PlayerPosX, g_PlayerPosY + 10 + hold_height, g_PlayerPosZ end end local tppObj = 0 local function findClearPos( x, y, z ) if tppEnt ~= nil and tppEnt ~= 0 and tppObj == 0 then tppObj = g_Entity[ tppEnt ].obj end local ny = y local found = false local numChecks = 0 while not found and numChecks < 100 do local obhit = IntersectAll( x, ny, z, x, ny + 50, z, tppObj ) or 0 if obhit == 0 then found = true else ny = ny + 10 numChecks = numChecks + 1 end end ny = max( ny, GetTerrainHeight( x, z ) + 50 ) return x, ny, z end local controlEnt = nil local lastTime = 0 local tDiff = 1 function grapplinghook3_main( e ) local gh = grappleHooks[ e ] local Ent = g_Entity[ e ] if controlEnt == nil then controlEnt = e end local timeNow = g_Time if controlEnt == e then if lastTime ~= 0 then tDiff = ( timeNow - lastTime ) / 20 end lastTime = timeNow end if gh == nil then return end --Prompt( gh.state ) if gh.state == "wait pick up" then if U.PlayerLookingNear( e, 150, 20 ) then PromptLocal( e, "Collect the " .. gh.name .. "? (E)" ) if g_KeyPressE == 1 and not epressed then epressed = true gh.state = "picking" end end elseif gh.state == "picking" then if g_KeyPressE == 0 then epressed = false StartTimer( e ) gh.state = "holding" end elseif gh.state == "holding" then local pax, pay, paz = rad( g_PlayerAngX ), rad( g_PlayerAngY ), rad( g_PlayerAngZ ) if g_MouseClick == 1 then ghtimer = GetTimer( e ) Panel( 40, 80, 64, 86 ) if ghtimer > max_charge_time then ghtimer = max_charge_time end perc = ( ( ghtimer / max_charge_time ) * 10 ) * 2 for a = 1, perc do Panel( 40 + a, 81, 41 + a, 82 ) end perc = ghtimer / max_charge_time n_max_time = max_throw_dist * perc perc = perc * 100 perc = round( perc, 0 ) TextCenterOnX( 50, 76, 3, perc .. " % " ) else if perc > 0 then CollisionOn( e ) perc = 0 gh.state = "firing" end ghtimer = 0 StartTimer( e ) end if gh.state ~= "firing" then local ppx, ppy, ppz = getPlayerEyePos() -- calculate carry position based on player angles local xo, yo, zo = U.Rotate3D( 0, 0, hold_dist, pax, pay, paz ) --yo = max( -20, yo ) -- position entity wrt player CollisionOff( e ) RotateObject( Ent.obj, g_PlayerAngX, g_PlayerAngY, g_PlayerAngZ ) PositionObject( Ent.obj, ppx + xo, ppy + yo, ppz + zo ) --CollisionOn( e ) end elseif gh.state == "firing" then local pax, pay, paz = rad( g_PlayerAngX ), rad( g_PlayerAngY ), rad( g_PlayerAngZ ) -- calculate a vector describing the direction of the throw local force = 80 * n_max_time / max_throw_dist local vx, vy, vz = U.Rotate3D( 0, 0, force, pax, pay, paz ) local ox, oy, oz = U.Rotate3D( 0.5, 0, 0, pax, pay, paz ) --AddObjectCollisionCheck( Ent.obj ) PushObject( Ent.obj, vx, vy, vz, ox, oy, oz ) gh.state = "fired" elseif gh.state == "fired" then if g_MouseClick == 2 then gh.state = "hit" gh.timer = timeNow + 4000 gh.hit = { x = Ent.x, y = Ent.y, z = Ent.z } end if gh.state == "hit" then CollisionOff( e ) RotateObject( Ent.obj, g_PlayerAngX, g_PlayerAngY, g_PlayerAngZ ) PositionObject( Ent.obj, gh.hit.x, gh.hit.y, gh.hit.z ) elseif GetTimer( e ) > n_max_time then gh.state = "return" end elseif gh.state == "hit" then local ppx, ppy, ppz = g_PlayerPosX, g_PlayerPosY, g_PlayerPosZ local pox, poy, poz = ( gh.hit.x - ppx ) * moveSpeed, ( gh.hit.y - ppy ) * moveSpeed, ( gh.hit.z - ppz ) * moveSpeed ppx, ppy, ppz = findClearPos( ppx + pox, ppy + poy, ppz + poz ) SetFreezePosition( ppx, ppy, ppz ) TransportToFreezePositionOnly() if U.PlayerCloserThanPos( gh.hit.x, gh.hit.y, gh.hit.z, 100 ) or timeNow > gh.timer then gh.state = "holding" end elseif gh.state == "return" then if not U.PlayerCloserThan( e, 150 ) then local x, y, z, xa, ya, za = GetEntityPosAng( e ) local ppx, ppy, ppz = getPlayerEyePos() local nox, noy, noz = ( ppx - x ) * returnSpeed, ( ppy - y ) * returnSpeed, ( ppz - z ) * returnSpeed -- move and rotate the entity local quat = Q.FromEuler( rad( xa ), rad( ya ), rad( za ) ) local rotquat = Q.FromEuler( 0, rad( rotSpeed * tDiff ), 0 ) xa, ya, za = Q.ToEuler( Q.Mul( quat, rotquat ) ) CollisionOff( e ) RotateObject( Ent.obj, deg( xa ), deg( ya ), deg( za ) ) PositionObject( Ent.obj, x + nox, y + noy, z + noz ) CollisionOn( e ) else gh.state = "holding" end end end