-- LUA Script - precede every function and global member with lowercase name of script + '_main' -- Default script - does nothing. local Q = require "scriptbank\\quatlib" local deg = math.deg local rad = math.rad local sin = math.sin local cos = math.cos local atan = math.atan2 local pi = math.pi local abs = math.abs local random = math.random local planes = {} function helicopter_init(e) Include("quatlib.lua") end local function CloserThan(Ent, dist) local dX, dZ = Ent.x - g_PlayerPosX, Ent.z - g_PlayerPosZ return (dX*dX + dZ*dZ) < dist*dist end local function TimerExpired(p) return g_Time > p.timer end local function SetTimerSecs(p, val) if TimerExpired(p) then p.timer = g_Time + val * 1000 end end local function PlayerAngle(Ent) -- gives angle to player from entity return 180 + deg(atan(Ent.x - g_PlayerPosX, Ent.z - g_PlayerPosZ)) end local function turnAngle(tA, cA) -- degrees local dA = tA - cA if dA > 0 then if dA < 180 then return dA end return -(360 - dA) else if dA > 180 then return -(360 + dA) end return dA end end local function Rotate3D (x, y, z, xrot, yrot, zrot) function RotatePoint2D (x, y, Ang) -- Ang in radians local Sa, Ca = sin(Ang), cos(Ang) return x*Ca - y*Sa, x*Sa + y*Ca end local NX, NY, NZ = x, y, z -- X NZ, NY = RotatePoint2D (NZ, NY, -xrot) -- Y NX, NZ = RotatePoint2D (NX, NZ, -yrot) -- Z NY, NX = RotatePoint2D (NY, NX, -zrot) return NX, NY, NZ end local function WrapAng(Ang) -- Ang in radians local P2 = pi * 2 local RAng = Ang while RAng > P2 do RAng = RAng - P2 end while RAng < -P2 do RAng = RAng + P2 end return RAng end local pitchQuat = {} local bankQuat = {} local function updatePosition(e, Ent, plane) local FPSrel = 1 / g_scheduler[e].frames_per_second plane.angleLeft = turnAngle(plane.targAngle, plane.currAngle) -- do rotation here if abs(plane.angleLeft) > 0.5 then local turnAmount = plane.turnSpeed * FPSrel if turnAmount > abs(plane.angleLeft) then turnAmount = abs(plane.angleLeft) end local turnQuat if plane.angleLeft > 0 then turnQuat = Q.FromEuler(0, rad(turnAmount), 0) plane.currAngle = plane.currAngle + turnAmount plane.targBank = 30 else turnQuat = Q.FromEuler(0, -rad(turnAmount), 0) plane.currAngle = plane.currAngle - turnAmount plane.targBank = -30 end plane.quat = Q.Mul(plane.quat, turnQuat) else plane.angleLeft = 0 if plane.state ~= 'fall' and plane.state ~= 'startfall' then plane.targBank = 0 end end local quat = plane.quat -------------------------------------------------------------- -- do bank here if plane.targBank ~= plane.currBank then local bA = (plane.targBank - plane.currBank) * FPSrel plane.currBank = bA + plane.currBank bankQuat[e] = Q.FromEuler(0, 0, rad(plane.currBank)) end if plane.currBank ~= 0 then quat = Q.Mul(quat, bankQuat[e]) end -------------------------------------------------------------- -- do pitch here if plane.targPitch ~= plane.currPitch then local pA = (plane.targPitch - plane.currPitch) * FPSrel plane.currPitch = pA + plane.currPitch pitchQuat[e] = Q.FromEuler(rad(plane.currPitch), 0, 0) end if plane.currPitch ~= 0 then quat = Q.Mul(quat, pitchQuat[e]) end -------------------------------------------------------------- local xA, yA, zA = Q.ToEuler(quat) -- change the vector to match 'forward' for the model, i.e. pointing up is 0,0,1 left would be -1,0,0 etc local vX, vY, vZ = Rotate3D (0, 0, 1, xA, yA, zA) local cX, cZ = vX * plane.speed * FPSrel, vZ * plane.speed * FPSrel -- climb code local cY = plane.climbSpeed * FPSrel plane.x, plane.y, plane.z = plane.x + cX, plane.y + cY, plane.z + cZ -- add collision tests here CollisionOff(e) ResetPosition(e, plane.x, plane.y, plane.z) ResetRotation(e, deg(xA), deg(yA), deg(zA)) CollisionOn(e) end function helicopter_main(e) local Ent = g_Entity[e] if Ent == nil then return end local plane = planes[e] if plane == nil then local Ang = WrapAng(rad(Ent.angley)) planes[e] = {x = Ent.x, y = Ent.y, z = Ent.z, quat = Q.FromEuler(0, Ang, 0), state = 'init', timer = 0, lasttime = 0, speed = 0, -- units per second turnSpeed = 0, -- degrees per second climbSpeed = 0, -- units per second targAngle = deg(Ang), currAngle = deg(Ang), angleLeft = 0, -- radians targPitch = 0, currPitch = 0, targBank = 0, currBank = 0, height = 0, -- units above terrain soundPlaying = false }; return end if plane.state ~= 'idle' and plane.state ~= 'init' then updatePosition(e, Ent, plane) end if plane.state == 'down' or not scheduler(e) then return end -- initialization if plane.state == 'init' then plane.state = 'idle' GravityOff(e) elseif plane.state == 'idle' and CloserThan(Ent, random(3000,4000)) then plane.state = 'takeoff' SetTimerSecs(plane, random(3,4)) plane.climbSpeed = random(80,120) -- taking off elseif plane.state == 'takeoff' then if TimerExpired(plane) then plane.state = 'takeoff2' SetTimerSecs(plane, random(3,4)) plane.targPitch = random(-35,-25) end elseif plane.state == 'takeoff2' then if not TimerExpired(plane) then plane.climbSpeed = plane.climbSpeed + random(6,9) plane.speed = plane.speed + random(18,22) else plane.state = 'fly' plane.targPitch = random(-15,-10) end -- flying straight elseif plane.state == 'fly' then if plane.climbSpeed > 0 then plane.climbSpeed = plane.climbSpeed - random(6,9) else plane.climbSpeed = 0 end if not CloserThan(Ent, random(7000,9000)) then plane.state = 'turn' plane.turnSpeed = random(20,30) plane.speed = plane.speed / 1.5 else if not plane.soundPlaying then LoopSound(e, 0) SetSoundVolume(80) plane.soundPlaying = true end end -- turning elseif plane.state == 'turn' then plane.targAngle = PlayerAngle(Ent) plane.state = 'turning' elseif plane.state == 'turning' then if plane.angleLeft == 0 and CloserThan(Ent, random(3000, 4000)) then plane.state = 'fly' plane.speed = plane.speed * 1.5 else plane.targAngle = PlayerAngle(Ent) end -- falling down elseif plane.state == 'startfall' then plane.targBank = random(-60,60) plane.targPitch = random(-60,60) plane.targAngle = random(0,359) plane.state = 'fall' elseif plane.state == 'fall' then if plane.speed > 10 then plane.speed = plane.speed - 7 end plane.climbSpeed = plane.climbSpeed - 5 if TimerExpired(plane) then SetTimerSecs(plane, 3) plane.targBank = random(-60,60) plane.targPitch = random(-60,60) plane.targAngle = random(0,359) end if Ent.y <= GetTerrainHeight(Ent.x, Ent.z) + 50 then -- if hit the ground plane.state = 'down' -- change plane.state to down plane.Timer = math.huge StopParticleEmitter(e) -- stop smoke SetEntityHealth(e, 0) end end -- if flying and is shot to death (while keeping entity alive), set falling plane.state and start smoke if Ent.health < 100 then SetEntityHealth(e, 1000) plane.state = 'startfall' StartParticleEmitter(e) StopSound(e, 0) SetTimerSecs(plane, 4) GravityOn(e) end end ---------------------------------------------------------------- -- Scheduler, this nifty little function keeps track of time. -- -- The main routine should be called every frame and returns -- -- a flag indicating whether the passed in time has expired -- -- since the last time it was called. -- -- If no time period is specified 100ms is used by default, -- -- i.e. 1/10th of a second. -- -- The caller can either ignore the return flag or use it to -- -- trigger time sensitive functionality. -- -- A global value giving the frames per second count is also -- -- generated, this can be used in script to make animations -- -- independent of frames. -- -- The reason this function is tied to an entity rather than -- -- being global is so that each entity can be triggered at a -- -- different time rather than all being triggered in the same -- -- frame. ---------------------------------------------------------------- g_scheduler = {} function scheduler(e, period) period = period or 100 -- defaults to tenths of a second (100ms) if g_Time == nil then return false end if g_scheduler[e] == nil then g_scheduler[e] = {frames_per_second = 60, period_accumulated = math.random(0, period), accumulated_time = 0, timer_value_last_frame = g_Time, frame_counter = 0}; -- 'test' mode sometimes doesn't clear Lua globals so if -- this appears to be the case initialise everything elseif g_scheduler[e].accumulated_time > 2000 then g_scheduler[e].frames_per_second = 60 g_scheduler[e].period_accumulated = math.random(0, period) g_scheduler[e].accumulated_time = 0 g_scheduler[e].timer_value_last_frame = g_Time g_scheduler[e].frame_counter = 0 end local entry = g_scheduler[e] local do_this_frame_flag = false entry.frame_counter = entry.frame_counter + 1 local time_since_last_frame = g_Time - entry.timer_value_last_frame if (entry.period_accumulated + time_since_last_frame) > period then entry.period_accumulated = (entry.period_accumulated + time_since_last_frame) - period; do_this_frame_flag = true else entry.period_accumulated = entry.period_accumulated + time_since_last_frame end if (entry.accumulated_time + time_since_last_frame) > 1000 then -- more than a second passed? entry.accumulated_time = (entry.accumulated_time + time_since_last_frame) - 1000; entry.frames_per_second = entry.frame_counter entry.frame_counter = 0 else entry.accumulated_time = entry.accumulated_time + time_since_last_frame end entry.timer_value_last_frame = entry.timer_value_last_frame + time_since_last_frame return do_this_frame_flag end