-- LUA Script - precede every function and global member with lowercase name of script + '_main' local Q = require "scriptbank\\quatlib" local U = require "scriptbank\\utillib" local P = require "scriptbank\\physlib" local rad = math.rad local deg = math.deg local abs = math.abs local cos = math.cos local min = math.min local max = math.max local atan = math.atan2 local posList = { [ 'Grand Clock 1c' ] = { base = 'Grand Clock 1a', typ = 'Pendulum', xo = 0, yo = 0, zo = 0 }, [ 'Grand Clock 1a' ] = { typ = 'Base' }, [ 'Grand Clock 1b' ] = { base = 'Grand Clock 1a', typ = 'None', xo = 0, yo = 0, zo = 0 }, [ 'Minute Hand' ] = { base = 'Grand Clock 1a', typ = 'Mhand', xo = 0, yo = 76.25, zo = -6.5 }, [ 'Hour Hand' ] = { base = 'Grand Clock 1a', typ = 'Hhand', xo = 0, yo = 76.25, zo = -6.53 } } local dynEnts = {} function gfclock_init_name( e, name ) Include( "quatlib.lua" ) Include( "utillib.lua" ) Include( "physlib.lua" ) dynEnts[ e ] = { name = name, state = 'init' } -- set health to a stupidly high value SetEntityHealthSilent( e, 1000000) end local function findBase( de ) for k, v in pairs( dynEnts ) do if posList[ v.name ].typ == 'Base' and v.state == 'ready' and v.atts[ de.name ] == 0 then return k end end end local function positionEnt( e, x, y, z, xa, ya, za ) CollisionOff( e ) ResetPosition( e, x, y, z ) ResetRotation( e, xa, ya, za ) CollisionOn( e ) end local lastTime = 0 local moveAmount = 0 local swingMax = 3.2 local swingSpeed = 0.07 function gfclock_main( e ) local de = dynEnts[ e ] if de == nil then return end local typ = posList[ de.name ].typ local timeNow = g_Time if timeNow > lastTime + 20 then moveAmount = ( timeNow - lastTime ) / 20 lastTime = timeNow end if de.state == 'done' then -- no more processing needed for this entity return elseif de.state == 'init' then if typ == 'Base' then -- first work out what our attachments are de.atts = {} for k, v in pairs( posList ) do if v.typ ~= 'Base' and v.base == de.name then de.atts[ k ] = 0 end end de.x, de.y, de.z, de.xa, de.ya, de.za = GetEntityPosAng( e ) -- now mark ourselves as ready de.state = 'ready' else -- must be attachment so look for a base that is ready -- to accept us local base = findBase( de ) if base == nil then return end local b = dynEnts[ base ] b.atts[ de.name ] = e local p = posList[ de.name ] local xo, yo, zo = U.Rotate3D( p.xo, p.yo, p.zo, rad( b.xa ), rad( b.ya ), rad( b.za ) ) de.x, de.y, de.z = b.x + xo, b.y + yo, b.z + zo positionEnt( e, de.x, de.y, de.z, b.xa, b.ya, b.za ) de.obj = g_Entity[ e ].obj de.baseObj = g_Entity[ base ].obj de.baseEnt = base de.state = 'idle' end elseif de.state == 'ready' then -- check if we have all attachments for k, v in pairs( de.atts ) do if v == 0 then PromptLocal( e, "Don't have a " .. k .. '!' ) return end end LoopSound( e, 0 ) de.state = 'done' elseif de.state == 'idle' then if typ == 'Pendulum' then de.state = 'swing' de.angle = 0 -- straight down de.direction = 'right' local _, _, _, xa, ya, za = GetEntityPosAng( e ) de.quat = Q.FromEuler( rad( xa ), rad( ya ), rad( za ) ) elseif typ == 'Hhand' or typ == 'Mhand' then de.timer = 0 de.state = 'rotating' local _, _, _, xa, ya, za = GetEntityPosAng( e ) de.quat = Q.FromEuler( rad( xa ), rad( ya ), rad( za ) ) else de.state = 'done' end elseif de.state == 'rotating' and timeNow > de.timer then de.timer = timeNow + 1000 local hours = tonumber( os.date( '%I' ) ) local mins = tonumber( os.date( '%M' ) ) local turnQ if typ == 'Mhand' then turnQ = Q.FromEuler( 0, 0, rad( -mins * 6 ) ) elseif typ == 'Hhand' then turnQ = Q.FromEuler( 0, 0, rad( -hours * 30 - mins / 2 ) ) end xa, ya, za = Q.ToEuler( Q.Mul( de.quat, turnQ ) ) positionEnt( e, de.x, de.y, de.z, deg( xa ), deg( ya ), deg( za ) ) if typ == 'Hhand' then return end if mins == 59 and de.minLast ~= 59 then if hours == 11 then if not de.preStrike then de.preStrike = true de.preStrikeCount = 40 end end elseif mins == 0 and de.minLast ~= 0 then if not de.strike then de.strike = true de.strikeTimer = 0 if hours == 0 then de.strikeNum = 12 else de.strikeNum = hours end end end de.minLast = mins if de.preStrike then if de.preStrikeCount <= 0 then PlaySound( de.baseEnt , 1 ) de.preStrike = false else de.preStrikeCount = de.preStrikeCount - 1 end end if de.strike then if de.strikeNum > 0 then if de.strikeTimer == 0 then PlaySound( de.baseEnt , 2 ) de.strikeNum = de.strikeNum - 1 elseif de.strikeTimer == 2 then PlaySound( de.baseEnt , 3 ) de.strikeNum = de.strikeNum - 1 end if de.strikeTimer < 3 then de.strikeTimer = de.strikeTimer + 1 else de.strikeTimer = 0 end else de.strike = false end end elseif de.state == 'swing' then local tweak = max( 0.05, cos( rad( abs( de.angle ) * 90 / swingMax ) ) ) if de.direction == 'right' then de.angle = de.angle + swingSpeed * moveAmount * tweak if de.angle >= swingMax then de.direction = 'left' de.angle = swingMax end else de.angle = de.angle - swingSpeed * moveAmount * tweak if de.angle <= -swingMax then de.direction = 'right' de.angle = -swingMax end end local turnQ = Q.FromEuler( 0, 0, rad( de.angle ) ) xa, ya, za = Q.ToEuler( Q.Mul( de.quat, turnQ ) ) local xo, yo, zo = U.Rotate3D( de.angle * 2, 0, 0, xa, ya, za ) positionEnt( e, de.x + xo, de.y + yo, de.z + zo, deg( xa ), deg( ya ), deg( za ) ) end end