-- LUA Script - precede every function and global member with lowercase name of script + '_main' -- Default script - does nothing. local Q = require "scriptbank\\quatlib" math.randomseed(os.time()) 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 -- make sure random number generator is seeded random(); random(); 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 WillCollide(x, y, z, x2, y2, z2) local dX, dY, dZ = x - x2, y - y2, z - z2 local coll = (dX*dX + dY*dY + dZ*dZ) < (300*300) local angle, ydiff = 0, 0 if coll then angle = 180 + deg(atan(dX, dZ)) ydiff = dY end return angle, ydiff 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 local nX, nY, nZ = plane.x + cX, plane.y + cY, plane.z + cZ local ccX1, ccZ1 = plane.x + cX * 60, plane.z + cZ * 60 local ccX2, ccZ2 = plane.x + cX * 120, plane.z + cZ * 120 local colAng, colY = 0,0 -- check for collision with other plane for k,v in pairs(planes) do if k ~= e then colAng, colY = WillCollide(nX, nY, nZ, v.x, v.y, v.z) if colAng == 0 and colY == 0 then colAng, colY = WillCollide(ccX1, nY, ccZ1, v.x, v.y, v.z) end if colAng == 0 and colY == 0 then colAng, colY = WillCollide(ccX2, nY, ccZ2, v.x, v.y, v.z) end if colAng ~= 0 or colY ~= 0 then break end end end if colAng ~= 0 then -- turn 90 right plane.targAngle = colAng + 45 end if colY ~= 0 and abs(colY) < 300 then -- too close to another plane so climb or decend if colY < 0 then plane.climbSpeed = plane.climbSpeed + 20 else plane.climbSpeed = plane.climbSpeed - 20 end end -- now check terrain if plane.height ~= 0 then if plane.state == 'fly' or plane.state == 'turn' or plane.state == 'turning' then local tD = GetTerrainHeight(ccX2, ccZ2) + plane.height - nY if tD > 0 then -- climb, climb, climb !!! plane.climbSpeed = tD elseif colY == 0 then -- maintain height tD = GetTerrainHeight(nX,nZ) + plane.height - nY plane.climbSpeed = tD end end end plane.x, plane.y, plane.z = nX, nY, nZ 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)) if deg(Ang) == 90 or deg(Ang) == 270 then Ang = Ang + 0.0001 end 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 }; if Ent.health < 100 then SetEntityHealth(e, 250) end return end if plane.state ~= 'idle' and plane.state ~= 'init' and plane.state ~= 'down' 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(5,7) 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 if plane.height == 0 then plane.height = plane.y - GetTerrainHeight(plane.x, plane.z) end 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 plane.y <= GetTerrainHeight(plane.x, plane.z) + 100 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