-- LUA Script - precede every function and global member with lowercase name of script + '_main' planes = {} -- Set Mode to 'enemy'/'friendly'/'neutral' function CallHelicopters(Num, Mode, fltTime, retToOrig, Height, Strength) local numHelis = Num if numHelis > 0 and Mode ~= nil then for k,v in pairs(planes) do if v.state == 'idle' then numHelis = numHelis - 1 -- set vars planes[k].state = 'call' if Mode ~= nil then planes[k].mode = Mode end if fltTime ~= nil then planes[k].fltTime = fltTime end if retToOrig then planes[k].retToOrigin = true end if Height ~= nil then planes[k].tgtHeight = Height end if Strength ~= nil then SetEntityHealth(k, Strength) end if numHelis == 0 then break end end end end return numHelis end 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 tan = math.tan local pi = math.pi local abs = math.abs local random = math.random local sqrt = math.sqrt local modf = math.modf -- make sure random number generator is seeded random(); random(); random() local playerLives function helicopter_init(e) Include("quatlib.lua") SetAnimationSpeed(e, 0) playerLives = g_PlayerLives end local function DistanceToTarget(p) local dX, dZ = p.x - p.targetX, p.z - p.targetZ return sqrt(dX*dX + dZ*dZ) end local function DistanceToPlayer(p) local dX, dZ = p.x - g_PlayerPosX, p.z - g_PlayerPosZ return sqrt(dX*dX + dZ*dZ) end local function Timer1Expired(p) return g_Time > p.timer1 end local function Timer2Expired(p) return g_Time > p.timer2 end local function SetTimer1Secs(p, val) p.timer1 = g_Time + val * 1000 end local function SetTimer2Secs(p, val) p.timer2 = g_Time + val * 1000 end local function TargetAngle(p) -- gives angle to target return 180 + deg(atan(p.x - p.targetX, p.z - p.targetZ)) end local function RandomPos(x, z, dist) dist = dist or 100 local angle = math.random() * 2 * math.pi return x + math.sin(angle) * dist, z + math.cos(angle) * dist end local function CloseTo(x, z, x2, z2) local dX, dZ = x - x2, z - z2 return (dX*dX + dZ*dZ) < 8000 * 8000 end ----------------------------------------------------------- -- this useful function iterates a list in order defined -- -- by sort function or by key order if none given -- ----------------------------------------------------------- local function sort_pairs(list, order_given) -- collect the keys local keys = {} for k in pairs(list) do keys[#keys+1] = k end if order_given then table.sort(keys, function(a,b) return order_given(list, a, b) end) else table.sort(keys) end -- return the iterator function local i = 0 return function() i = i + 1 if keys[i] then return keys[i], list[keys[i]] end end end local aiListTimer = 0 local aiTargetList = {} local aiTargetted = {} local function BuildAITargetList() if ai_bot_state == nil then return end if g_Time < aiListTimer then return end aiListTimer = g_Time + 1000 local aiUnSortedList = {} local pX, pZ = g_PlayerPosX, g_PlayerPosZ for k,_ in pairs(ai_bot_state) do local aiEnt = g_Entity[k] if aiEnt.health > 0 and aiEnt.active == 1 then local dX, dZ = pX - aiEnt.x, pZ - aiEnt.z local sqDist = (dX*dX + dZ*dZ) if sqDist < 8000 * 8000 then aiUnSortedList[k] = sqDist end end end aiTargetList = {} local index = 1 for k,_ in sort_pairs(aiUnSortedList, function(list,a,b) return list[a] < list[b] end) do aiTargetList[index] = k index = index + 1 end end local function ShowEnemies() for k,t in ipairs(aiTargetList) do if not aiTargetted[t] then PromptLocal(t, k) else PromptLocal(t, k .. ", tgt" ) end end end local function FindAiTarget() for k,t in ipairs(aiTargetList) do if not aiTargetted[t] then aiTargetted[t] = true return t end end end local function UpdateTarget(p, doNow) if p.mode == 'enemy' then p.targetX = g_PlayerPosX p.targetZ = g_PlayerPosZ p.targetY = g_PlayerPosY elseif p.mode == 'friendly' then -- check if already attacking AI character if p.targAiEnt ~= nil then if not (g_Entity[p.targAiEnt].health > 0) then -- enemy dead so find another p.targAiEnt = FindAiTarget() end if p.targAiEnt ~= nil then p.targetX = g_Entity[p.targAiEnt].x p.targetZ = g_Entity[p.targAiEnt].z p.targetY = g_Entity[p.targAiEnt].y else p.targetX = 0 end else -- Check if any specified target points available if p.targetX == 0 and htGetTarget ~= nil then p.targetX, p.targetZ, p.targetY = htGetTarget(p) end -- if no targetting spots available grab an AI and have at them if p.targetX == 0 then if p.targAiEnt == nil then p.targAiEnt = FindAiTarget() end if p.targAiEnt ~= nil then p.targetX = g_Entity[p.targAiEnt].x p.targetZ = g_Entity[p.targAiEnt].z p.targetY = g_Entity[p.targAiEnt].y else -- failed to find anything to attack so land p.state = 'land1' end end end else -- neutral so just amble about the sky if doNow or random(1,200) == 1 then p.targetX, p.targetZ = RandomPos(g_PlayerPosX, g_PlayerPosZ, random(5000, 15000)) end end end local function CloseEnough(p1, p2) local dX, dY, dZ = p1.x - p2.x, p1.y - p2.y, p1.z - p2.z return (dX*dX + dY*dY + dZ * dZ) < (2000 * 2000) end local function SetCollisionFlag(e, plane) plane.checkCols = false for k,v in pairs(planes) do if k ~= e then if CloseEnough (plane, v) then plane.checkCols = true break end end end 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) local RAng = Ang while RAng >= 360 do RAng = RAng - 360 end while RAng < 0 do RAng = RAng + 360 end return RAng end function HeliAddRocket(e) for k,v in pairs(planes) do if v.state == 'rearm' then for i = 1,4 do if v.rockets[i] == nil then planes[k].rockets[i] = e planes[k].hadRockets = true return true end end end end return false end local function fireRocket(p, tDist) local fireAngle = rad(90 - p.currPitch) local fireDist = tan(fireAngle) * (p.y - p.targetY) if fireDist < (tDist - 800) or fireDist > (tDist + 800) then return end for i = 1,4 do if p.rockets[i] ~= nil and random (1,8) == 1 then if hrFire ~= nil then hrFire(p.rockets[i], p.currQuat) p.rockets[i] = nil end end end end local function rocketsLeft(p) for i = 1,4 do if p.rockets[i] ~= nil then return true end end return false end local function doRocket(e, x, y, z, xo, yo, zo, xA, yA, zA) local XO, YO, ZO = Rotate3D (xo, yo, zo, xA, yA, zA) ResetPosition(e, x + XO, y + YO, z + ZO) ResetRotation(e, deg(xA), deg(yA), deg(zA)) end local function displayRockets(p, xA, yA, zA) if p.rockets[1] ~= nil then doRocket(p.rockets[1], p.x, p.y, p.z, -88, 32, 25, xA, yA, zA) end if p.rockets[2] ~= nil then doRocket(p.rockets[2], p.x, p.y, p.z, -58, 32, 25, xA, yA, zA) end if p.rockets[3] ~= nil then doRocket(p.rockets[3], p.x, p.y, p.z, 58, 32, 25, xA, yA, zA) end if p.rockets[4] ~= nil then doRocket(p.rockets[4], p.x, p.y, p.z, 88, 32, 25, xA, yA, zA) end end local pitchQuat = {} local bankQuat = {} local function updatePosition(e, plane) local FPSrel = 1 / g_scheduler[e].frames_per_second plane.angleLeft = turnAngle(plane.targAngle, plane.currAngle) -- do rotation here if plane.state == 'fly' or plane.state == 'turn' or plane.state == 'turning' or plane.state == 'land1' or plane.state == 'land2' then 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 plane.currAngle = WrapAng(plane.currAngle) end 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 plane.currQuat = quat -------------------------------------------------------------- local xA, yA, zA = Q.ToEuler(quat) -- change the vector to match 'forward' for the model, i.e. pointing up is 0,1,0 left would be 1,0,0 etc local vX, vY, vZ = Rotate3D (0, 0, 1, xA, yA, zA) if plane.speed ~= plane.targSpeed then local sD = (plane.targSpeed - plane.speed) / 50 if abs(sD) < 1 then plane.speed = plane.targSpeed else plane.speed = plane.speed + sD end end 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, colType = 0,0,'close' if plane.checkCols then -- check for collision with other plane for k,v in pairs(planes) do if k ~= e and v.checkCols then colAng, colY = WillCollide(nX, nY, nZ, v.x, v.y, v.z) if plane.state ~= 'land2' then 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) if colAng ~= 0 or colY ~= 0 then colType = 'far' end else colType = 'near' end end if colAng ~= 0 or colY ~= 0 then break end end end end -- avoid buildings local lsX, lsY, lsZ = Rotate3D (-250, 0, 20, xA, yA, zA) local leX, leY, leZ = Rotate3D (-250, 0, plane.speed * 200 * FPSrel, xA, yA, zA) local rsX, rsY, rsZ = Rotate3D ( 250, 0, 20, xA, yA, zA) local reX, reY, reZ = Rotate3D ( 250, 0, plane.speed * 200 * FPSrel, xA, yA, zA) local objl = IntersectAll(plane.x + lsX, plane.y - 10, plane.z + lsZ, plane.x + leX, plane.y - 10, plane.z + leZ , plane.obj) local objr = IntersectAll(plane.x + rsX, plane.y - 10, plane.z + rsZ, plane.x + reX, plane.y - 10, plane.z + reZ , plane.obj) if objl and objl ~= 0 then -- turn right colAng = 0 plane.targAngle = WrapAng(plane.currAngle + 60) elseif objr and objr ~= 0 then -- turn left colAng = 0 plane.targAngle = WrapAng(plane.currAngle - 60) end if colAng ~= 0 then if colType == 'close' then plane.targAngle = WrapAng(colAng + 180) elseif colType == 'near' then plane.targAngle = WrapAng(colAng + 90) else plane.targAngle = WrapAng(colAng + 45) end end if plane.state ~= 'fall' and colY ~= 0 and abs(colY) < 200 then -- too close to another plane so climb or decend if colY < 0 then plane.climbSpeed = plane.climbSpeed + 10 else plane.climbSpeed = plane.climbSpeed - 10 end end -- now check terrain if colY == 0 or plane.state == 'land2' then if plane.state == 'fly' or plane.state == 'turn' or plane.state == 'turning' or plane.state == 'land1' then local tD = GetTerrainHeight(ccX2, ccZ2) + plane.height - nY if tD > 0 then -- climb, climb, climb !!! if tD > 200 then tD = 200 end plane.climbSpeed = tD elseif colY == 0 then -- maintain height tD = GetTerrainHeight(nX,nZ) + plane.height - nY if tD < -200 then tD = -200 end plane.climbSpeed = tD end elseif plane.state == 'land2'then local tD = GetTerrainHeight(nX, nZ) + plane.height - nY plane.climbSpeed = tD / 2 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)) displayRockets(plane, xA, yA, 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(Ent.angley) if Ang == 90 or Ang == 270 then Ang = Ang + 0.0001 end planes[e] = {x = Ent.x, y = Ent.y, z = Ent.z, quat = Q.FromEuler(0, rad(Ang), 0), currQuat = Q.FromEuler(0, rad(Ang), 0), state = 'init', mode = 'enemy', obj = Ent.obj, fltTime = math.huge, retToOrigin = false, origX = Ent.x, origY = Ent.y, origZ = Ent.z, timer1 = 0, timer2 = 0, targSpeed = 0, speed = 0, -- units per second turnSpeed = 0, -- degrees per second climbSpeed = 0, -- units per second targAngle = Ang, currAngle = Ang, angleLeft = 0, -- radians targPitch = 0, currPitch = 0, targBank = 0, currBank = 0, tgtHeight = 1600, height = 0, -- units above terrain targAiEnt = nil, -- only applies in 'friendly' mode targetX = 0, targetZ = 0, targetY = 0, hasBlade = false, checkCols = false, rockets = {}, hadRockets = false, playerLives = g_PlayerLives, soundVolume = 90, animSpeed = 0, restartSound = false, soundPlaying = false }; if Ent.health < 100 then SetEntityHealth(e, 250) end return end if plane.state ~= 'idle' and plane.state ~= 'init' and plane.state ~= 'spinup' and plane.state ~= 'down' and plane.state ~= 'rearm' then updatePosition(e, plane) end if plane.state == 'down' or not scheduler(e) then return end if plane.state == 'fly' and plane.mode == 'friendly' then BuildAITargetList() end -- ShowEnemies() -- PromptLocal(e, plane.mode .. "," .. plane.state .. "," .. (plane.targAiEnt or 'none')) -- when the player dies all sound seems to stop so this -- ugly code is here to detect this and restart the sounds if plane.playerLives > g_PlayerLives then plane.restartSound = plane.soundPlaying SetTimer1Secs(plane, random() * 0.4 + 2) plane.playerLives = g_PlayerLives end if plane.restartSound then if Timer1Expired(plane) then LoopSound(e, 2) SetSoundVolume(plane.soundVolume) plane.restartSound = false end else if plane.soundPlaying then SetSound(e, 2) SetSoundVolume(plane.soundVolume - 5) end end local targetDist = DistanceToTarget(plane) local playerDist = DistanceToPlayer(plane) plane.soundVolume = 100 - (25 * playerDist / 12000) musicVolume = 100 - (20 * playerDist / 12000) -- initialization if plane.state == 'init' then plane.state = 'rearm' GravityOff(e) -- allow a couple of seconds for rockets to attach SetTimer1Secs(plane, 8) elseif plane.state == 'rearm' then if plane.animSpeed > 0 then plane.animSpeed = plane.animSpeed - 0.05 end SetAnimationSpeed(e, plane.animSpeed) if Timer1Expired(plane) then plane.state = 'idle' SetAnimationSpeed(e, 0) end local xA, yA, zA = Q.ToEuler(plane.quat) displayRockets(plane, xA, yA, zA) elseif plane.state == 'idle' and not g_callMechanismActive and playerDist < 4000 then plane.state = 'call' elseif plane.state == 'call' then plane.state = 'spinup' SetTimer1Secs(plane, random(6,8)) elseif plane.state == 'spinup' then if plane.animSpeed < 1 then plane.animSpeed = plane.animSpeed + 0.05 end SetAnimationSpeed(e, plane.animSpeed) if Timer1Expired(plane) then plane.climbSpeed = random(80,120) SetTimer1Secs(plane, random(3,4)) plane.state = 'takeoff' SetAnimationSpeed(e, 2) end -- taking off elseif plane.state == 'takeoff' then if Timer1Expired(plane) then plane.state = 'takeoff2' SetTimer1Secs(plane, random(3,4)) plane.targPitch = random(35,45) end if not plane.soundPlaying then LoopSound(e, 2) SetSoundVolume(plane.soundVolume) plane.soundPlaying = true end elseif plane.state == 'takeoff2' then if not Timer1Expired(plane) then plane.climbSpeed = plane.climbSpeed + random(5,7) plane.targSpeed = plane.targSpeed + random(18,22) else plane.state = 'fly' if plane.mode == 'neutral' then UpdateTarget(plane, true) end SetTimer2Secs(plane, plane.fltTime) if hlClearLanding ~= nil then hlClearLanding(plane) end plane.targPitch = random(15,20) end -- flying straight elseif plane.state == 'fly' then if Timer2Expired(plane) then plane.state = 'land1' plane.timer2 = math.huge return elseif plane.hadRockets and not rocketsLeft(plane) then plane.hadRockets = false SetTimer2Secs(plane, 10) end 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) if abs(plane.height - plane.tgtHeight) > 500 then plane.height = plane.tgtHeight + random(-400, 400) end end end if not (targetDist < random(8000,10000)) then plane.state = 'turn' plane.turnSpeed = random(20,30) plane.targSpeed = plane.targSpeed / 1.5 end -- turning elseif plane.state == 'turn' then UpdateTarget(plane) plane.targAngle = TargetAngle(plane) plane.state = 'turning' elseif plane.state == 'turning' then if plane.angleLeft == 0 and (plane.mode == 'neutral' or targetDist < random(3000, 4000)) then plane.state = 'fly' plane.targSpeed = plane.targSpeed * 1.5 elseif plane.mode ~= 'neutral' then UpdateTarget(plane) local pA = TargetAngle(plane) if abs(plane.currAngle - pA) < 1 and targetDist < 7000 and not (targetDist < 3000) then if mfHeliShoot ~= nil then if random(1,3) == 1 then mfHeliShoot(e) if mfHeliSmoke ~= nil then mfHeliSmoke(e) end PlaySoundIfSilent(e, 1) SetSoundVolume(plane.soundVolume) end end if random(1,3) == 1 then fireRocket(plane, targetDist) end else plane.targAngle = pA end 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.targSpeed > 10 then plane.targSpeed = plane.targSpeed - 7 end plane.climbSpeed = plane.climbSpeed - 5 if Timer1Expired(plane) then SetTimer1Secs(plane, 3) plane.targBank = random(-60,60) plane.targPitch = random(-60,60) plane.targAngle = random(0,359) SetAnimationSpeed(e, 0.25) 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 elseif plane.state == 'land1' then if plane.targAiEnt ~= nil then aiTargetted[plane.targAiEnt] = false plane.targAiEnt = nil end if plane.retToOrigin or hlGetLanding == nil then plane.targetX, plane.targetY, plane.targetZ = plane.origX, plane.origY, plane.origZ else plane.targetX, plane.targetY, plane.targetZ = hlGetLanding(plane) end if plane.targetX + plane.targetY + plane.targetZ ~= 0 then plane.turnSpeed = random(20,30) plane.targAngle = TargetAngle(plane) plane.state = 'land2' else plane.state = 'fly' plane.targetX = 0 UpdateTarget(plane, true) SetTimer2Secs(plane, plane.fltTime) end elseif plane.state == 'land2' then if targetDist < 1000 then if targetDist > 500 then plane.targSpeed = 80 plane.height = 500 + plane.targetY elseif targetDist > 100 then plane.targSpeed = 40 plane.height = 100 + plane.targetY plane.targBank = 0 plane.targPitch = 0 elseif targetDist > 50 then plane.targSpeed = 20 plane.height = plane.targetY else plane.targSpeed = 0 end end if plane.y <= GetTerrainHeight(plane.x, plane.z) + 1 then plane.state = 'rearm' StopSound(e, 2) plane.soundPlaying = false plane.targetX = 0 -- allow a couple of seconds for rockets to attach SetTimer1Secs(plane, 6) else plane.targAngle = TargetAngle(plane) 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' SetAnimationSpeed(e, 0.5) StartParticleEmitter(e) StopSound(e, 2) plane.soundPlaying = false SetTimer1Secs(plane, 4) GravityOn(e) else SetCollisionFlag(e, plane) 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