-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- file: gui_music.lua -- brief: yay music -- author: cake -- -- Copyright (C) 2007. -- Licensed under the terms of the GNU GPL, v2 or later. -- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- function widget:GetInfo() return { name = "Music Player", desc = "Plays music based on situation", author = "cake, trepan, Smoth, Licho, xponen", date = "Mar 01, 2008, Aug 20 2009, Nov 23 2011", license = "GNU GPL, v2 or later", layer = 0, enabled = true -- loaded by default? } end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- options_path = "Settings/Audio" options = { useIncludedTracks = { name = "Use Included Tracks", type = "bool", value = true, desc = "Use the tracks included with Zero-K", noHotkey = true, }, pausemusic = { name = "Pause Music", type = "bool", value = false, desc = "Music pauses with game", noHotkey = true, }, } local unitExceptions = include("Configs/snd_music_exception.lua") local windows = {} local warThreshold = 5000 local peaceThreshold = 1000 local PLAYLIST_FILE = "sounds/music/playlist.lua" local LOOP_BUFFER = 0.015 -- if looping track is this close to the end, go ahead and loop local UPDATE_PERIOD = 1 local musicType = "peace" local warPointsIter = 1 -- Position in circular buffer. 1-indexed because L[ew]a local warPointsSize = 30 -- Size of circular buffer. Sampling is currently hardcoded but might change later. local warPointsFriendly = {} -- keeps track of the number of doods killed in each time frame local warPointsHostile = {} local warPointsRollover = 4000000000 -- Roll back to zero after this many have accumulated local timeframetimer = 0 local timeframetimer_short = 0 local loopTrack = "" local previousTrack = "" local previousTrackType = "" local newTrackWait = 1000 local numVisibleEnemy = 0 local fadeVol local curTrac = "no name" local songText = "no name" local haltMusic = false local looping = false local paused = false local lastTrackTime = -1 local tracks local firstTime = false local wasPaused = false local firstFade = true local initSeed = 0 local initialized = false local gameStarted = Spring.GetGameFrame() > 0 local gameOver = false local myTeam = Spring.GetMyTeamID() local isSpec = Spring.GetSpectatingState() or Spring.IsReplay() local defeat = false -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- local function GetMusicType() return musicType end local function StartLoopingTrack(trackInit, trackLoop) if not (VFS.FileExists(trackInit) and VFS.FileExists(trackLoop)) then Spring.Log(widget:GetInfo().name, LOG.ERROR, "Missing one or both tracks for looping") end haltMusic = true Spring.StopSoundStream() musicType = "custom" curTrack = trackInit loopTrack = trackLoop Spring.PlaySoundStream(trackInit, WG.music_volume or 0.5) looping = 0.5 end local function StartTrack(track) if not tracks.peace then Spring.Echo("Missing tracks.peace file, no music started") return end haltMusic = false looping = false Spring.StopSoundStream() local newTrack = previousTrack if musicType == "custom" then previousTrackType = "peace" musicType = "peace" end if (not gameStarted) then musicType = "briefing" end if track then newTrack = track -- play specified track musicType = "custom" else local numTypeTracks = #tracks[musicType] if (numTypeTracks == 0) then return end for tries=1,10 do newTrack = tracks[musicType][math.random(1, numTypeTracks)] if newTrack ~= previousTrack then break end end end -- for key, val in pairs(oggInfo) do -- Spring.Echo(key, val) -- end firstFade = false previousTrack = newTrack -- if (oggInfo.comments.TITLE and oggInfo.comments.TITLE) then -- Spring.Echo("Song changed to: " .. oggInfo.comments.TITLE .. " By: " .. oggInfo.comments.ARTIST) -- else -- Spring.Echo("Song changed but unable to get the artist and title info") -- end curTrack = newTrack Spring.PlaySoundStream(newTrack,WG.music_volume or 0.5) WG.music_start_volume = WG.music_volume end local function StopTrack(noContinue) looping = false Spring.StopSoundStream() if noContinue then haltMusic = true else haltMusic = false StartTrack() end end local function SetWarThreshold(num) if num and num >= 0 then warThreshold = num else warThreshold = 5000 end end local function SetPeaceThreshold(num) if num and num >= 0 then peaceThreshold = num else peaceThreshold = 1000 end end local function InitializeTracks() if VFS.FileExists(PLAYLIST_FILE, VFS.RAW_FIRST) then local plTracks = VFS.Include(PLAYLIST_FILE, nil, VFS.RAW_FIRST) tracks.war = plTracks.war tracks.peace = plTracks.peace tracks.briefing = plTracks.briefing tracks.victory = plTracks.victory tracks.defeat = plTracks.defeat end local vfsMode = (options.useIncludedTracks.value and VFS.RAW_FIRST) or VFS.RAW tracks.war = tracks.war or VFS.DirList("sounds/music/war/", "*.ogg", vfsMode) tracks.peace = tracks.peace or VFS.DirList("sounds/music/peace/", "*.ogg", vfsMode) tracks.briefing = tracks.briefing or VFS.DirList("sounds/music/briefing/", "*.ogg", vfsMode) tracks.victory = tracks.victory or VFS.DirList("sounds/music/victory/", "*.ogg", vfsMode) tracks.defeat = tracks.defeat or VFS.DirList("sounds/music/defeat/", "*.ogg", vfsMode) end function widget:Update(dt) if gameOver then return end if not initialized then math.randomseed(os.clock()* 100) initialized=true -- these are here to give epicmenu time to set the values properly -- (else it's always default at startup) InitializeTracks() end timeframetimer_short = timeframetimer_short + dt if timeframetimer_short > 0.03 then local playedTime, totalTime = Spring.GetSoundStreamTime() playedTime = tonumber( ("%.2f"):format(playedTime) ) paused = (playedTime == lastTrackTime) lastTrackTime = playedTime if looping then if looping == 0.5 then looping = 1 elseif playedTime >= totalTime - LOOP_BUFFER then Spring.StopSoundStream() Spring.PlaySoundStream(loopTrack, WG.music_volume or 0.5) end end timeframetimer_short = 0 end -- (Spring.GetGameRulesParam("recentNukeLaunch") == 1) -- Might need this for superweapon music later timeframetimer = timeframetimer + dt if (timeframetimer > UPDATE_PERIOD) then -- every second timeframetimer = 0 newTrackWait = newTrackWait + 1 numVisibleEnemy = 0 local doods = Spring.GetVisibleUnits(-1, nil, true) for i=1,#doods do if (Spring.IsUnitAllied(doods[i]) ~= true) then numVisibleEnemy = numVisibleEnemy + 1 end end local totalKilled, friendliesKilled, hostilesKilled = 0, 0, 0 local iLast = ((warPointsIter-11) % warPointsSize) + 1 -- Look back 10 periods. local iLast2 = ((warPointsIter-21) % warPointsSize) + 1 -- Look back 20 periods. -- Last 10 seconds count for double friendliesKilled = friendliesKilled + ((warPointsFriendly[warPointsIter] - warPointsFriendly[iLast]) % warPointsRollover) friendliesKilled = friendliesKilled + ((warPointsFriendly[warPointsIter] - warPointsFriendly[iLast2]) % warPointsRollover) hostilesKilled = hostilesKilled + ((warPointsHostile[warPointsIter] - warPointsHostile[iLast]) % warPointsRollover) hostilesKilled = hostilesKilled + ((warPointsHostile[warPointsIter] - warPointsHostile[iLast2]) % warPointsRollover) totalKilled = (friendliesKilled * 1.5) + hostilesKilled -- Roll to next index in the circular buffers, continue cumulative sum local iNext = (warPointsIter % warPointsSize) + 1 warPointsFriendly[iNext] = warPointsFriendly[warPointsIter] % warPointsRollover warPointsHostile[iNext] = warPointsHostile[warPointsIter] % warPointsRollover warPointsIter = iNext if (musicType == "war" or musicType == "peace") then if (totalKilled >= warThreshold) then musicType = "war" elseif (totalKilled <= peaceThreshold) then musicType = "peace" end end if (not firstTime) then StartTrack() firstTime = true end local playedTime, totalTime = Spring.GetSoundStreamTime() playedTime = math.floor(playedTime) totalTime = math.floor(totalTime) --Spring.Echo(playedTime, totalTime) --Spring.Echo(playedTime, totalTime, newTrackWait) --if((totalTime - playedTime) <= 6 and (totalTime >= 1) ) then --Spring.Echo("time left:", (totalTime - playedTime)) --Spring.Echo("volume:", (totalTime - playedTime)/6) --if ((totalTime - playedTime)/6 >= 0) then -- Spring.SetSoundStreamVolume((totalTime - playedTime)/6) --else -- Spring.SetSoundStreamVolume(0.1) --end --elseif(playedTime <= 5 )then--and not firstFade --Spring.Echo("time playing:", playedTime) --Spring.Echo("volume:", playedTime/5) --Spring.SetSoundStreamVolume( playedTime/5) --end --Spring.Echo(previousTrackType, musicType) if ( previousTrackType == "peace" and musicType == "war" ) or (playedTime >= totalTime) -- both zero means track stopped and not(haltMusic or looping) then previousTrackType = musicType StartTrack() --Spring.Echo("Track: " .. newTrack) newTrackWait = 0 end local _, _, paused = Spring.GetGameSpeed() if (paused ~= wasPaused) and options.pausemusic.value then Spring.PauseSoundStream() wasPaused = paused end end end function widget:GameStart() if not gameStarted then gameStarted = true previousTrackType = musicType musicType = "peace" StartTrack() end --Spring.Echo("Track: " .. newTrack) newTrackWait = 0 end -- Safety of a heisenbug function widget:GameFrame() widget:GameStart() widgetHandler:RemoveCallIn("GameFrame") end function widget:UnitDamaged(unitID, unitDefID, unitTeam, damage, paralyzer) if unitExceptions[unitDefID] then return end if (damage < 1.5) then return end if (UnitDefs[unitDefID] == nil) then return end if paralyzer then return end local multifactor = 1 if (numVisibleEnemy > 3) then multifactor = math.log(numVisibleEnemy) end if (teamID == myTeam) then warPointsFriendly[warPointsIter] = warPointsFriendly[warPointsIter] + (damage * multifactor); else warPointsHostile[warPointsIter] = warPointsHostile[warPointsIter] + (damage * multifactor); end end function widget:UnitDestroyed(unitID, unitDefID, teamID) if unitExceptions[unitDefID] then return end local unitWorth = 50 if (UnitDefs[unitDefID].metalCost > 500) then unitWorth = 200 end if (UnitDefs[unitDefID].metalCost > 1000) then unitWorth = 300 end if (UnitDefs[unitDefID].metalCost > 3000) then unitWorth = 500 end if (UnitDefs[unitDefID].metalCost > 8000) then unitWorth = 700 end local multifactor = 1 if (numVisibleEnemy > 3) then multifactor = math.log(numVisibleEnemy) end if (teamID == myTeam) then warPointsFriendly[warPointsIter] = warPointsFriendly[warPointsIter] + (unitWorth*multifactor); else warPointsHostile[warPointsIter] = warPointsHostile[warPointsIter] + (unitWorth*multifactor); end end function widget:TeamDied(team) if team == myTeam and not isSpec then defeat = true end end local function PlayGameOverMusic(gameWon) local track if gameWon then if #tracks.victory <= 0 then return end track = tracks.victory[math.random(1, #tracks.victory)] musicType = "victory" else if #tracks.defeat <= 0 then return end track = tracks.defeat[math.random(1, #tracks.defeat)] musicType = "defeat" end looping = false Spring.StopSoundStream() Spring.PlaySoundStream(track,WG.music_volume or 0.5) WG.music_start_volume = WG.music_volume end function widget:GameOver() PlayGameOverMusic(not defeat) end function widget:Initialize() WG.Music = WG.Music or {} WG.Music.StartTrack = StartTrack WG.Music.StartLoopingTrack = StartLoopingTrack WG.Music.StopTrack = StopTrack WG.Music.SetWarThreshold = SetWarThreshold WG.Music.SetPeaceThreshold = SetPeaceThreshold WG.Music.GetMusicType = GetMusicType WG.Music.PlayGameOverMusic = PlayGameOverMusic -- Spring.Echo(math.random(), math.random()) -- Spring.Echo(os.clock()) -- for TrackName,TrackDef in pairs(tracks.peace) do -- Spring.Echo("Track: " .. TrackDef) -- end --math.randomseed(os.clock()* 101.01)--lurker wants you to burn in hell rgn -- for i=1,20 do Spring.Echo(math.random()) end for i = 1, warPointsSize do warPoints[i] = 0 warPointsFriendly[i] = 0 warPointsHostile[i] = 0 end end function widget:Shutdown() Spring.StopSoundStream() WG.Music = nil for i=1,#windows do (windows[i]):Dispose() end end -------------------------------------------------------------------------------- --------------------------------------------------------------------------------