-- LUA Script - precede every function and global member with lowercase name of script + '_main' local U = require "scriptbank\\utillib" local sub = string.sub local find = string.find local sqrt = math.sqrt local acos = math.acos local sin = math.sin local random = math.random local soundPts = {} local playingList = {} local multipleDone = {} local defaultTriggerDist = 1000 local defaultMinVolume = 60 local defaultMaxVolume = 99 local defaultPeriodMin = 100 local defaultPeriodMax = 5000 local function GetArgs( str, sep ) local sep = sep or ',' local pos = 1 local list = {} local length = #str while pos < length + 1 do local nextPos = find( str, sep, pos + 1 ) or length if nextPos < length then list[ #list + 1 ] = sub( str, pos, nextPos - 1 ) else list[ #list + 1 ] = sub( str, pos, nextPos ) break end pos = nextPos + 1 end return list end function sound_control_init_name( e, name ) Include( "utillib.lua" ) local myArgs = GetArgs( name, '_' ) if myArgs == {} then PromptDuration( "No Args", 10000 ) return end local dist = defaultTriggerDist local volL = defaultMinVolume local volH = defaultMaxVolume local numS = 1 if myArgs[ 2 ] == nil then myArgs[ 2 ] = 'Loop' else if myArgs[ 3 ] ~= nil then dist = tonumber( myArgs[ 3 ] ) end if myArgs[ 4 ] ~= nil then volL = tonumber( myArgs[ 4 ] ) end if myArgs[ 5 ] ~= nil then volH = tonumber( myArgs[ 5 ] ) end if myArgs[ 6 ] ~= nil then numS = tonumber( myArgs[ 6 ] ) end if numS > 5 then numS = 5 elseif numS < 1 then numS = 1 end if dist < 0 or volL < 0 or volH < 0 then PromptDuration( "Negative Args not allowed", 10000 ) return end end local perL = defaultPeriodMin local perH = defaultPeriodMax if myArgs[ 2 ] == 'Periodic' or myArgs[ 2 ] == 'PeriodicRandom' then if myArgs[ 7 ] ~= nil then perL = tonumber( myArgs[ 7 ] ) end if myArgs[ 8 ] ~= nil then perH = tonumber( myArgs[ 8 ] ) end if perL < 0 or perH < 0 then PromptDuration( "Negative Args not allowed", 10000 ) return end end soundPts[ e ] = { state = 'init', name = myArgs[ 1 ], typ = myArgs[ 2 ], dist = dist, volL = volL, volH = volH, perL = perL, perH = perH, numS = numS } end local function sqdistToPoint( x, z, p ) local dX, dZ = p.x - x, p.z - z return dX*dX + dZ*dZ end local function distToPoint( x, z, p ) return sqrt( sqdistToPoint( x, z, p ) ) end local function distToPoints( x, z, p1, p2 ) local a2 = sqdistToPoint( x, z, p1 ) local b2 = sqdistToPoint( x, z, p2 ) local c2 = sqdistToPoint( p1.x, p1.z, p2 ) local a, b, c = sqrt( a2 ), sqrt( b2 ), sqrt( c2 ) -- now for some funky math local A = acos( ( b2 + c2 - a2 ) / ( 2 * b * c ) ) local B = acos( ( a2 + c2 - b2 ) / ( 2 * a * c ) ) if A > B then dist = sin( B ) * a else dist = sin( A ) * b end return dist end local function getClosestPoints( x, z, name ) local ld local p1d, p2d = math.huge, math.huge local pos1, pos2 local p1Id, p2Id = 0, 0 -- first find closest point for k, v in pairs( soundPts ) do if v.name == name then ld = sqdistToPoint( x, z, v.pos ) if ld < p1d then p1d = ld pos1 = v p1Id = k end end end if pos1 == nil then return end -- find next closest point for k, v in pairs( soundPts ) do if v.name == name and v ~= pos1 then ld = sqdistToPoint( x, z, v.pos ) if ld < p2d then p2d = ld pos2 = v p2Id = k end end end if pos2 == nil then return end return pos1, pos2 end local function canTrigger( v, dist, sndPt1, sndPt2 ) if dist < v.dist then if v.typ ~= 'PlayZone' and v.typ ~= 'LoopZone' then return true end if not v.multiple and v.Ent.plrinzone == 1 then return true end if v.multiple and sndPt1 ~= nil and sndPt2 ~= nil and ( sndPt1.Ent.plrinzone == 1 or sndPt2.Ent.plrinzone == 1 ) then return true end end return false end local function playPeriodic( v, timeNow, dist, volume ) v.periods = {} local per = v.perH - v.perL v.pTimer = timeNow + v.perL + random() * ( per * dist / v.dist ) v.pVolume = volume v.pDist = dist end local function updatePeriodic( e, v, timeNow, dist, volume ) if volume ~= nil then v.pVolume = volume v.pDist = dist return end local tDiff = 0 local sound = -1 if v.typ == 'Random' then if timeNow > v.pTimer then sound = random( 1, v.numS ) - 1 end else tDiff = v.pTimer - timeNow if tDiff < v.perL then local perInt = v.perL / v.numS for i = 1, v.numS do if tDiff < perInt * i then sound = i - 1 break end end elseif timeNow > v.pTimer then sound = 0 tDiff = 0 end end if sound ~= -1 then --PromptLocal( e, sound .. ", " .. tDiff ) local per = ( v.pDist / v.dist ) * ( v.perH - v.perL ) PlaySoundIfSilent( e, sound ) SetSound( e, sound ) SetSoundVolume( v.volL + v.pVolume * ( v.volH - v.volL ) ) if v.typ == 'Random' then v.pTimer = timeNow + v.perL + random() * ( v.perH - v.perL ) elseif v.typ == 'PeriodicRandom' then v.pTimer = timeNow + v.perL + random() * per + tDiff else v.pTimer = timeNow + v.perL + per + tDiff end end end local zTime = 0 function sound_control_main( e ) local sound = soundPts[ e ] if sound == nil then return end if sound.state == 'init' then local Ent = g_Entity[ e ] sound.pos = { x = Ent.x, z = Ent.z } sound.multiple = false sound.Ent = Ent sound.state = 'find' elseif sound.state == 'find' then if not sound.multiple then for k, v in pairs( soundPts ) do if not v.multiple and k ~= e and v.name == sound.name then v.multiple = true sound.multiple = true end end end sound.state = 'done' end local timeNow = g_Time if ( sound.typ == 'Periodic' or sound.typ == 'PeriodicRandom' or sound.typ == 'Random' ) and playingList[ e ] then updatePeriodic( e, sound, timeNow ) end if timeNow < zTime then return end zTime = timeNow + 50 local ppx, ppz = g_PlayerPosX, g_PlayerPosZ for k, v in pairs( soundPts ) do if v.state == 'done' and not multipleDone[ v.name ] then local dist = math.huge local sndPt1, sndPt2 if not v.multiple then dist = distToPoint( ppx, ppz, v.pos ) else sndPt1, sndPt2 = getClosestPoints( ppx, ppz, v.name ) if sndPt1 ~= nil and sndPt2 ~= nil then dist = distToPoints( ppx, ppz, sndPt1.pos, sndPt2.pos ) multipleDone[ v.name ] = true end end --PromptLocal( k, v.state .. ", " .. dist ) if canTrigger( v, dist, sndPt1, sndPt2 ) then local volume = 1 - ( dist * dist ) / ( v.dist * v.dist ) if volume > 0 then if not playingList[ k ] then playingList[ k ] = true if v.typ == 'Loop' or v.typ == 'LoopZone' then for i = 1, v.numS do LoopSound( k, i - 1 ) SetSound( k, i - 1 ) SetSoundVolume( v.volL + volume * ( v.volH - v.volL ) ) end elseif v.typ == 'Play' or v.typ == 'PlayZone' then for i = 1, v.numS do PlaySoundIfSilent( k, i - 1 ) SetSound( k, i - 1 ) SetSoundVolume( v.volL + volume * ( v.volH - v.volL ) ) end elseif v.typ == 'Random' or v.typ == 'Periodic' or v.typ == 'PeriodicRandom' then playPeriodic( v, timeNow, dist, volume ) end else if v.typ == 'Random' or v.typ == 'Periodic' or v.typ == 'PeriodicRandom' then updatePeriodic( k, v, timeNow, dist, volume ) else for i = 1, v.numS do SetSound( k, i - 1 ) SetSoundVolume( v.volL + volume * ( v.volH - v.volL ) ) end end end else for i = 1, v.numS do StopSound( k, i - 1 ) end playingList[ k ] = false end elseif playingList[ k ] then for i = 1, v.numS do StopSound( k, i - 1 ) end playingList[ k ] = false end end end multipleDone = {} end