代码拉取完成,页面将自动刷新
-- **MINOS AUDIO**
AudioState = {}
OnAnyLoad{
function( triggerArgs )
DeferredPlayVoiceLines = {}
if MusicName ~= nil and MusicId == nil then
-- Out of sync (presumably from a load)
local restoreTrackName = MusicName
local restoreMusicSection = MusicSection
MusicName = nil
MusicSection = nil
MusicPlayer( restoreTrackName )
SetMusicSection( restoreMusicSection )
if MusicId ~= nil then
if MusicActiveStems ~= nil then
SetSoundCueValue({ Names = MusicActiveStems, Id = MusicId, Value = 1 })
end
if MusicMutedStems ~= nil then
SetSoundCueValue({ Names = MusicMutedStems, Id = MusicId, Value = 0 })
end
else
MusicActiveStems = nil
MusicMutedStems = nil
end
if MusicPlayerTrackPaused and MusicPlayerTrackData[MusicName] ~= nil then
PauseMusic()
end
end
if AmbientTrackName ~= nil and AmbientMusicSource ~= nil and AmbientMusicId == nil then
-- Out of sync (presumably from a load)
local restoreTrackName = AmbientTrackName
AmbientTrackName = nil
wait(0.02) -- Need to wait for potential stored targets to spawn
local source = ActiveEnemies[AmbientMusicSource.ObjectId]
if source ~= nil then
MusicianMusic( source, { TrackName = restoreTrackName } )
if CurrentDeathAreaRoom ~= nil then
if CurrentDeathAreaRoom.AmbientMusicParams ~= nil then
for param, value in pairs( CurrentDeathAreaRoom.AmbientMusicParams ) do
SetSoundCueValue({ Id = AmbientMusicId, Name = param, Value = value, Duration = 0.5 })
end
end
SetVolume({ Id = AmbientMusicId, Value = CurrentDeathAreaRoom.AmbientMusicVolume, Duration = 0.5 })
end
end
end
end
}
function DeferredAudioScripts()
for index, params in ipairs(DeferredPlayVoiceLines) do
thread( PlayVoiceLinesReal, params[1], params[2], params[3], params[4] )
end
DeferredPlayVoiceLines = {}
end
-- **Music**
function MusicPlayerEvent( source, args )
MusicPlayer( args.TrackName, args.MusicInfo, args.DestinationId )
end
function MusicPlayer( trackName, musicInfo, destinationId )
if trackName == nil then
return false
end
if MusicName ~= nil and MusicName == trackName then
-- Don't play an identical track that's already playing
-- But do still update the source if it is being changed
SetSoundSource({ Id = MusicId, DestinationId = destinationId })
return false
end
if MusicId ~= nil then
-- Quick cut any music still playing
StopSound({ Id = MusicId, Duration = 0.25 })
MusicId = nil
end
if StoppingMusicId ~= nil then
-- Quick cut any music still fading out
StopSound({ Id = StoppingMusicId, Duration = 0.25 })
StoppingMusicId = nil
end
MusicName = trackName
MusicId = PlaySound({ Name = MusicName, AddCallbacks = true, Id = destinationId })
SetDefaultMusicParams( MusicName, MusicId )
if musicInfo ~= nil then
musicInfo.Id = MusicId
musicInfo.Name = MusicName
end
if SecretMusicId ~= nil then
-- Secret music has priority and is mutually exclusive so this must wait
SetVolume({ Id = MusicId, Value = 0.0, Duration = 0.0 })
PauseSound({ Id = MusicId, Duration = 0.0 })
end
return true
end
function SecretMusicPlayer( trackName, musicInfo )
if trackName == nil then
return false
end
if SecretMusicName == trackName then
-- Don't play an identical track that's already playing
return
end
if MusicId ~= nil then
-- Quick cut any music still playing
PauseMusic()
end
if SecretMusicId ~= nil then
-- Quick cut any music still playing
StopSound({ Id = SecretMusicId, Duration = 0.25 })
end
SecretMusicName = trackName
SecretMusicId = PlaySound({ Name = SecretMusicName, AddCallbacks = true })
SetDefaultMusicParams( SecretMusicName, SecretMusicId )
if musicInfo ~= nil then
musicInfo.Id = SecretMusicId
musicInfo.Name = SecretMusicName
end
return true
end
function StopSecretMusic( smoothStop )
if SecretMusicId == nil then
return
end
if smoothStop then
EndMusic( SecretMusicId, SecretMusicName )
else
StopSound({ Id = SecretMusicId, Duration = 0.25 })
end
SecretMusicId = nil
SecretMusicName = nil
end
function ChaosBassStart()
SetSoundCueValue({ Names = { "ChaosBass" }, Id = SecretMusicId, Value = 1, Duration = 0.5 })
end
function ChaosBassStop()
SetSoundCueValue({ Names = { "ChaosBass" }, Id = SecretMusicId, Value = 0, Duration = 3 })
end
function SingingPresentation( source, ars )
if source.SingingFx ~= nil then
CreateAnimation({ Name = source.SingingFx, DestinationId = source.ObjectId, OffsetX = source.SingingAnimOffsetX or source.AnimOffsetX, OffsetZ = source.AnimOffsetZ, Group = "Combat_UI_World" })
end
if source.SingingAnimation ~= nil then
SetAnimation({ Name = source.SingingAnimation, DestinationId = source.ObjectId })
end
if source.PartnerSingingAnimation ~= nil and source.PartnerObjectId ~= nil then
SetAnimation({ Name = source.PartnerSingingAnimation, DestinationId = source.PartnerObjectId })
end
end
function MusicianMusic( source, args )
if CurrentRun.BlockAmbientMusic then
return
end
CurrentRun.EventState[source.ObjectId] = { FunctionName = "SingingPresentation", Args = args }
SingingPresentation( source, args )
if args.TrackName == AmbientTrackName then
-- Don't play an identical track that's already playing
-- But do still update the source if it is being changed
SetSoundSource({ Id = AmbientMusicId, DestinationId = source.ObjectId })
AmbientMusicSource = source
return
end
if AmbientMusicId ~= nil then
-- Quick cut the previously playing id
StopSound({ Id = AmbientMusicId, Duration = 0.25 })
AmbientMusicId = nil
end
--Shake({ Id = source.ObjectId, Distance = 1, Speed = 3, Duration = 9999, Angle = 0 })
AmbientMusicSource = source
AmbientMusicId = PlaySound({ Name = args.TrackName, Id = source.ObjectId })
SetSoundCueValue({ Names = { "Vocals", }, Id = AmbientMusicId, Value = 1 })
AmbientTrackName = args.TrackName
SetVolume({ Id = AmbientMusicId, Value = 1 })
if args.TrackOffsetMin ~= nil then
SetSoundPosition({ Id = AmbientMusicId, Position = RandomFloat( args.TrackOffsetMin, args.TrackOffsetMax ) })
end
-- Workaround for FMOD bug, after a long play-session VO played in 2D can become inaudible. Pausing and unpausing the sound fixes it.
thread( PauseUnpauseSoundWorkaround, AmbientMusicId )
end
-- Workaround for FMOD bug, after a long play-session VO played in 2D can become inaudible. Pausing and unpausing the sound fixes it.
function PauseUnpauseSoundWorkaround( soundId )
wait( 0.03 )
PauseSound({ Id = soundId, Duration = 0 })
ResumeSound({ Id = soundId, Duration = 0 })
end
function StopMusicianMusic( source, args )
StopSound({ Id = AmbientMusicId, Duration = args.Duration or 0.2 })
AmbientMusicId = nil
AmbientTrackName = nil
if source ~= nil and source.ObjectId ~= nil then
CurrentRun.EventState[source.ObjectId] = nil
end
end
function SetDefaultMusicParams( trackName, musicId )
SetSoundCueValue({ Names = { "Keys" }, Id = musicId, Value = 1 })
SetSoundCueValue({ Names = { "Guitar", "Drums", "Bass" }, Id = musicId, Value = 1 })
SetMusicSection( 1, musicId )
end
function RandomStemMixer( currentRoom, musicId )
if musicId == nil then
return
end
if currentRoom.IgnoreStemMixer then
return
end
local musicSetup = RandomInt( 1, currentRoom.RandomStemMixerOptions or 3 )
if musicSetup == 1 then
-- guitar, bass, drums
SetSoundCueValue({ Names = { "Guitar" }, Id = musicId, Value = 1, Duration = 2.5 })
SetSoundCueValue({ Names = { "Bass" }, Id = musicId, Value = 1, Duration = 2.5 })
SetSoundCueValue({ Names = { "Drums" }, Id = musicId, Value = 1, Duration = 0.25 })
elseif musicSetup == 2 then
-- drums only
SetSoundCueValue({ Names = { "Guitar" }, Id = musicId, Value = 0, Duration = 10 })
SetSoundCueValue({ Names = { "Bass" }, Id = musicId, Value = 0, Duration = 10 })
SetSoundCueValue({ Names = { "Drums" }, Id = musicId, Value = 1, Duration = 0.25 })
elseif musicSetup == 3 then
-- bass and drums only
SetSoundCueValue({ Names = { "Guitar" }, Id = musicId, Value = 0, Duration = 10 })
SetSoundCueValue({ Names = { "Bass" }, Id = musicId, Value = 1, Duration = 2.5 })
SetSoundCueValue({ Names = { "Drums" }, Id = musicId, Value = 1, Duration = 0.25 })
else
-- guitar and drums only
SetSoundCueValue({ Names = { "Guitar" }, Id = musicId, Value = 1, Duration = 10 })
SetSoundCueValue({ Names = { "Bass" }, Id = musicId, Value = 0, Duration = 2.5 })
SetSoundCueValue({ Names = { "Drums" }, Id = musicId, Value = 1, Duration = 0.25 })
end
end
function MusicMixer( mixArgs )
if mixArgs == nil then
return
end
if mixArgs.MusicMixerRequirements ~= nil and not IsGameStateEligible( CurrentRun, mixArgs, mixArgs.MusicMixerRequirements ) then
return
end
wait( mixArgs.MusicStartDelay )
if mixArgs.PlayBiomeMusic then
local biomeName = CurrentRun.CurrentRoom.RoomSetName
local biomeMusicTracks = MusicTrackData[biomeName]
if biomeMusicTracks ~= nil then
if BiomeMusicPlayCounts[biomeName] == nil then
BiomeMusicPlayCounts[biomeName] = 0
end
local trackIndex = (BiomeMusicPlayCounts[biomeName] % #biomeMusicTracks) + 1
local trackData = biomeMusicTracks[trackIndex]
MusicPlayer( trackData.Name, trackData )
BiomeMusicPlayCounts[biomeName] = BiomeMusicPlayCounts[biomeName] + 1
end
end
if MusicId == nil then
return
end
if mixArgs.MusicActiveStems ~= nil then
MusicActiveStems = mixArgs.MusicActiveStems
SetSoundCueValue({ Names = mixArgs.MusicActiveStems, Id = MusicId, Value = 1, Duration = 0.75 })
end
if mixArgs.MusicMutedStems ~= nil then
MusicMutedStems = mixArgs.MusicMutedStems
SetSoundCueValue({ Names = mixArgs.MusicMutedStems, Id = MusicId, Value = 0, Duration = mixArgs.MusicMutedStemsDuration or 0.75 })
end
if mixArgs.MusicSection ~= nil then
SetMusicSection( mixArgs.MusicSection )
end
if mixArgs.UseRoomMusicSection ~= nil and CurrentRun.CurrentRoom.MusicSection ~= nil then
SetMusicSection( CurrentRun.CurrentRoom.MusicSection )
end
end
function CheckMusicEvents( currentRun, musicEvents )
if musicEvents == nil then
return
end
for k, musicEvent in ipairs( musicEvents ) do
if IsGameStateEligible( currentRun, musicEvent, musicEvent.GameStateRequirements ) then
if musicEvent.EndMusic then
EndMusic()
end
thread( MusicMixer, musicEvent )
end
end
end
function StartBossRoomMusic()
SetMusicSection( 2 )
local activeStemTable = { "Guitar", "Bass", "Drums" }
SetSoundCueValue({ Names = activeStemTable, Id = MusicId, Value = 1, Duration = 0.75 })
end
function EndMusic( musicId, musicName, hardStopTime )
if musicId == nil then
musicId = MusicId
end
if musicName == nil then
musicName = MusicName
end
if hardStopTime == nil then
hardStopTime = 20
end
if musicId == nil then
return
end
SetMusicSection( 10, musicId )
if hardStopTime ~= nil then
StopSound({ Id = musicId, Value = 0.0, Duration = hardStopTime })
StoppingMusicId = musicId
end
if musicId == MusicId then
MusicId = nil
MusicName = nil
MusicSection = nil
end
end
function PauseMusic()
PauseSound({ Id = MusicId, Duration = 0.2 })
end
function ResumeMusic( duration, delay )
if MusicId == nil then
return
end
wait( delay )
ResumeSound({ Id = MusicId, Duration = duration or 0.2 })
end
function PauseAmbientMusic()
PauseSound({ Id = AmbientMusicId, Duration = 0.2 })
PauseSound({ Id = SecretMusicId, Duration = 0.2 })
end
function ResumeAmbientMusic()
ResumeSound({ Id = AmbientMusicId, Duration = 0.2 })
SetVolume({ Id = AmbientMusicId, Value = 1.0 })
ResumeSound({ Id = SecretMusicId, Duration = 0.2 })
end
function SetMusicSection( section, musicId )
if section == nil then
return
end
if musicId == nil then
musicId = MusicId
end
SetSoundCueValue({ Names = { "Section", }, Id = musicId, Value = section })
if musicId == MusicId then
MusicSection = section
MusicSectionStartDepth = CurrentRun.RunDepthCache
end
end
function SetAdvancedTooltipMixing( value )
value = value or 0
SetSoundCueValue({ Id = GetMixingId({}), Names = { "Wagon" }, Value = value })
end
function IsMusicPlaying()
if MusicId ~= nil and MusicId > 0 then
return true
end
return false
end
-- Music Marker logic
OnMusicMarker{ "Marker End",
function( triggerArgs )
local markerName = triggerArgs.name
if markerName == "Marker End" then
notifyExistingWaiters( "MusicTrackEnded" )
end
end
}
OnMusicMarker{ "Shout",
function( triggerArgs )
if not AllowShout then
return
end
if MusicName ~= "/Music/MusicExploration4_MC" and MusicName ~= "/Music/MusicHadesReset_MC" then
return
end
if CurrentRun.Hero.LastKillTime == nil or _worldTime > CurrentRun.Hero.LastKillTime + 20 then
return
end
if IsEmpty( RequiredKillEnemies ) then
return
end
PlaySound({ Name = "/SFX/Shout1" })
AllowShout = false
end
}
-- **AMBIENCE**
function StartRoomAmbience( currentRun, currentRoom )
local newTrackName = nil
if currentRoom.Encounter then
newTrackName = currentRoom.Encounter.Ambience
end
newTrackName = newTrackName or currentRoom.Ambience
if newTrackName == "None" then
-- Silence requested
StopSound({ Id = AmbienceId, Duration = 0.5 })
AmbienceId = nil
AmbienceName = nil
elseif newTrackName ~= nil then
-- Specific track requested
if newTrackName ~= AmbienceName then
StopSound({ Id = AmbienceId, Duration = 0.5 })
AmbienceId = PlaySound({ Name = newTrackName, Duration = 0.5 })
AmbienceName = newTrackName
end
else
-- Nothing requested, use default biome track
local ambienceTrackName = nil
local biomeAmbienceTracks = AmbienceTracks[currentRoom.RoomSetName]
if biomeAmbienceTracks ~= nil then
local trackIndex = (GetCompletedRuns() % #biomeAmbienceTracks) + 1
local trackData = biomeAmbienceTracks[trackIndex]
if trackData.Name ~= nil and trackData.Name ~= AmbienceName then
StopSound({ Id = AmbienceId, Duration = 0.5 })
AmbienceId = PlaySound({ Name = trackData.Name, Duration = 0.5 })
AmbienceName = trackData.Name
end
if trackData.ReverbValue ~= nil then
SetAudioEffectState({ Name = "Reverb", Value = trackData.ReverbValue })
end
end
end
if currentRoom.ReverbValue ~= nil then
SetAudioEffectState({ Name = "Reverb", Value = currentRoom.ReverbValue })
end
end
function EndAmbience( duration )
StopSound({ Id = AmbienceId, Duration = duration or 0.2 })
AmbienceId = nil
StopSound({ Id = AmbientMusicId, Duration = duration or 0.2 })
AmbientMusicId = nil
AmbientTrackName = nil
end
-- **VO**
-- Minos Voice Debug
OnKeyPressed{ "Alt V", Name = "ToggleVoice",
function( triggerArgs )
if CurrentRun.CurrentRoom.IntroVO ~= nil then
PlayRemainingSpeech( CurrentRun.CurrentRoom.IntroVO )
end
end
}
function AudioInit()
PlayedRandomLines = PlayedRandomLines or {}
AllowShout = true
BiomeMusicPlayCounts = BiomeMusicPlayCounts or {}
end
function PlayRandomEligibleVoiceLines( voiceLineSets )
while not IsEmpty( voiceLineSets ) do
local voiceLines = RemoveRandomValue( voiceLineSets )
local playedSomething = PlayVoiceLines( voiceLines, true )
if playedSomething then
return
end
end
end
function PlayFirstEligibleVoiceLines( voiceLineSets )
local highestIndex = GetHighestIndex( voiceLineSets )
for index = 1, highestIndex do
local voiceLines = voiceLineSets[index]
if voiceLines ~= nil then
local playedSomething = PlayVoiceLines( voiceLines, true )
if playedSomething then
return
end
end
end
end
function PlayVoiceLines( voiceLines, neverQueue, source, args )
if args ~= nil and args.Defer then
for k, v in pairs(DeferredPlayVoiceLines) do
if v[1] == voiceLines then
--DebugPrint({ Text = "voice lines play request de-duped"})
return
end
end
table.insert( DeferredPlayVoiceLines, { voiceLines, neverQueue, source, args } )
return
end
return PlayVoiceLinesReal( voiceLines, neverQueue, source, args )
end
function PlayVoiceLinesReal( voiceLines, neverQueue, source, args )
if voiceLines == nil then
return
end
args = args or {}
source = GetLineSource( voiceLines, source, args )
if source == nil then
-- Never play a line if the source doesn't exist
return
end
if not IsVoiceLineEligible( CurrentRun, voiceLines, nil, nil, source, args ) then
-- First check requirements of the whole set
if voiceLines.PlayedNothingFunctionName ~= nil then
local playedNothingFunction = _G[voiceLines.PlayedNothingFunctionName]
if playedNothingFunction ~= nil then
playedNothingFunction( source, voiceLines.PlayedNothingFunctionArgs )
end
end
return
end
if source.PlayingVoiceLines then
if voiceLines.Queue == "Interrupt" then
-- Play as normal
else
if neverQueue then
--DebugPrint({ Text = "Skipped voiceLines on "..GetTableString( source.Name, source ) })
return
end
if source.QueuedVoiceLines == nil then
source.QueuedVoiceLines = {}
end
table.insert( source.QueuedVoiceLines, voiceLines )
--DebugPrint({ Text = "Queued voiceLines on "..GetTableString( source.Name, source ) })
return
end
end
local playedSomething = false
local parentLine = voiceLines
source.PlayingVoiceLines = true
if PlayVoiceLine( voiceLines, nil, nil, source, args ) then
playedSomething = true
else
if voiceLines.PlayedNothingFunctionName ~= nil then
local playedNothingFunction = _G[voiceLines.PlayedNothingFunctionName]
if playedNothingFunction ~= nil then
playedNothingFunction( source, voiceLines.PlayedNothingFunctionArgs )
end
end
end
source.PlayingVoiceLines = false
if not IsEmpty( source.QueuedVoiceLines ) then
local nextVoiceLines = RemoveFirstValue( source.QueuedVoiceLines )
if PlayVoiceLines( nextVoiceLines, false, source, args ) then
playedSomething = true
end
end
return playedSomething
end
function GetLineSource( line, source, args )
if line.ObjectType ~= nil then
local typeIds = GetIdsByType({ Name = line.ObjectType })
if IsEmpty( typeIds ) then
return nil
end
local objectId = typeIds[1]
source = ActiveEnemies[objectId]
if source == nil then
return nil
else
if line.RequiredSourceValue ~= nil and not source[line.RequiredSourceValue] then
return nil
end
if line.RequiredSourceValueFalse ~= nil and source[line.RequiredSourceValueFalse] then
return nil
end
return source
end
end
if line.UsePlayerSource then
return CurrentRun.Hero
end
if line.Source ~= nil then
return line.Source
end
if source ~= nil then
return source
end
return CurrentRun.Hero
end
function PlayVoiceLine( line, prevLine, parentLine, source, args )
local playedSomething = false
if parentLine == nil then
parentLine = line
end
args = args or {}
args.ThreadName = line.ThreadName or args.ThreadName
args.Queue = line.Queue or args.Queue
args.BreakIfPlayed = line.BreakIfPlayed or args.BreakIfPlayed
args.PlayOnceContext = line.PlayOnceContext or args.PlayOnceContext
args.SubtitleMinDistance = line.SubtitleMinDistance or args.SubtitleMinDistance
args.Actor = line.Actor or args.Actor
args.AllowTalkOverTextLines = line.AllowTalkOverTextLines or args.AllowTalkOverTextLines
-- By default, the player object is the ListenerId, though could be something else
args.ListenerId = line.ListenerId or args.ListenerId or CurrentRun.Hero.ObjectId
source = GetLineSource( line, source, args )
if source == nil then
-- Never play a line if the source doesn't exist
return
end
if line.SetFlagTrue ~= nil then
GameState.Flags[line.SetFlagTrue] = true
end
if line.SetFlagFalse ~= nil then
GameState.Flags[line.SetFlagFalse] = false
end
if line.TriggerCooldowns ~= nil then
for k, cooldownName in pairs( line.TriggerCooldowns ) do
TriggerCooldown( cooldownName )
end
end
-- Play this line
if line.Cue ~= nil then
if args.OnPlayedSomethingFunctionName ~= nil and not args.PlayedSomething then
local onPlayedSomethingFunction = _G[args.OnPlayedSomethingFunctionName]
if onPlayedSomethingFunction ~= nil then
thread( onPlayedSomethingFunction, source, args.OnPlayedSomethingFunctionArgs )
end
end
if line.PreLineThreadedFunctionName ~= nil then
local preLineThreadedFunction = _G[line.PreLineThreadedFunctionName]
thread(preLineThreadedFunction, source, line.PreLineThreadedFunctionArgs )
end
wait( line.PreLineWait or parentLine.PreLineWait, args.ThreadName )
local preLineAnim = line.PreLineAnim or parentLine.PreLineAnim
if preLineAnim ~= nil then
SetAnimation({ Name = preLineAnim, DestinationId = source.ObjectId })
end
local playedSpeechId = 0
local useSubtitles = false
if not source.Mute then
if args.SubtitleMinDistance then
local dist = GetDistance({ Id = args.ListenerId, DestinationId = source.ObjectId })
if dist > args.SubtitleMinDistance then
useSubtitles = false
else
useSubtitles = true
end
else
useSubtitles = true
end
if line.NoTarget or parentLine.NoTarget then
playedSpeechId = PlaySpeech({ Name = line.Cue, Queue = args.Queue, SubtitleColor = source.SubtitleColor, UseSubtitles = useSubtitles, Actor = args.Actor })
elseif line.SkipAnim or parentLine.SkipAnim then
playedSpeechId = PlaySpeechCueFromSource( line.Cue, source, false, args.Queue, useSubtitles, source.SubtitleColor, args )
else
playedSpeechId = PlaySpeechCueFromSource( line.Cue, source, true, args.Queue, useSubtitles, source.SubtitleColor, args )
end
end
if line.UseOcclusion then
SetSoundCueValue({ Id = playedSpeechId, Names = { "VoiceOcclusion" }, Value = 1.0, Duration = 0.01 })
end
if playedSpeechId > 0 then
prevLine = line
LastLinePlayed = line.Cue
table.insert( CurrentRun.CurrentRoom.VoiceLinesPlayed, line.Cue )
playedSomething = true
args.PlayedSomething = true
-- @refactor The SpeechRecord recording is pretty scattered / redundant
SpeechRecord[line.Cue] = true
CurrentRun.SpeechRecord[line.Cue] = true
if args.PlayOnceContext ~= nil then
GameState.SpeechRecordContexts[args.PlayOnceContext] = GameState.SpeechRecordContexts[args.PlayOnceContext] or {}
GameState.SpeechRecordContexts[args.PlayOnceContext][line.Cue] = true
end
-- Intentionally leaving this on raw data for now to be wiped out on load
line.LastPlayTime = _worldTime
parentLine.LastPlayTime = _worldTime
waitUntil( line.Cue )
wait( line.PostLineWait or parentLine.PostLineWait, args.ThreadName )
if line.PostLineFunctionName ~= nil then
local postLineFunction = _G[line.PostLineFunctionName]
postLineFunction( source, line.PostLineFunctionArgs )
end
if args.BreakIfPlayed then
return playedSomething
end
else
--DebugAssert({ Condition = playedSpeechId > 0, Text = "Speech failed to play: "..line.Cue })
end
end
-- Play sublines
if line.RandomRemaining then
local eligibleUnplayedLines = {}
local allEligibleLines = {}
for k, subLine in ipairs( line ) do
if IsVoiceLineEligible( CurrentRun, subLine, prevLine, line, source, args ) then
table.insert( allEligibleLines, subLine )
if not PlayedRandomLines[subLine.Cue] then
table.insert( eligibleUnplayedLines, subLine )
end
end
end
if not IsEmpty( allEligibleLines ) then
local randomLine = nil
if IsEmpty( eligibleUnplayedLines ) then
-- All lines played, start the record over
for k, subLine in ipairs( line ) do
PlayedRandomLines[subLine.Cue] = nil
end
randomLine = GetRandomValue( allEligibleLines )
else
randomLine = GetRandomValue( eligibleUnplayedLines )
end
PlayedRandomLines[randomLine.Cue] = true
-- Effectively pass down by value rather than reference
local subLineArgs = ShallowCopyTable( args )
if PlayVoiceLine( randomLine, prevLine, line, source, subLineArgs ) then
prevLine = randomLine
playedSomething = true
args.PlayedSomething = true
if args.BreakIfPlayed or randomLine.BreakIfPlayed or subLineArgs.BreakIfPlayed then
return playedSomething
end
end
end
else
for k, subLine in ipairs( line ) do
if IsVoiceLineEligible( CurrentRun, subLine, prevLine, line, source, args ) then
-- Effectively pass down by value rather than reference
local subLineArgs = ShallowCopyTable( args )
if PlayVoiceLine( subLine, prevLine, line, source, subLineArgs ) then
prevLine = subLine
playedSomething = true
args.PlayedSomething = true
if args.BreakIfPlayed or subLine.BreakIfPlayed or subLineArgs.BreakIfPlayed then
return playedSomething
end
end
end
end
end
return playedSomething
end
function CleanUpCurrentSpeechId( cue, speechId, source, animation )
if speechId == nil then
return
end
waitUntil( cue )
if animation ~= nil then
if StopStatusAnimation( source, animation ) and not source.BlockStatusAnimations and ConfigOptionCache.ShowUIAnimations and ConfigOptionCache.ShowSpeechBubble then
local endAnimation = animation.."_End"
CreateAnimation({ Name = endAnimation, DestinationId = source.ObjectId, OffsetX = source.AnimOffsetX, OffsetZ = source.AnimOffsetZ })
end
end
if CurrentSpeechId == speechId then
CurrentSpeechId = nil
end
end
function PlaySpeechCue( cue, source, animation, queue, useSubtitles, subtitleColor, args )
if cue == nil or cue == "" then
return 0
end
args = args or {}
local sourceId = nil
if source ~= nil then
sourceId = source.ObjectId
end
local speechId = PlaySpeech({ Name = cue, Id = sourceId, Queue = queue, UseSubtitles = useSubtitles, SubtitleColor = subtitleColor, Actor = args.Actor })
if speechId > 0 then
if source ~= nil and animation ~= nil and ConfigOptionCache.ShowUIAnimations and ConfigOptionCache.ShowSpeechBubble then
PlayStatusAnimation( source, { Animation = animation } )
end
CurrentSpeechId = speechId
-- @refactor The SpeechRecord recording is pretty scattered / redundant
SpeechRecord[cue] = true
CurrentRun.SpeechRecord[cue] = true
thread( CleanUpCurrentSpeechId, cue, speechId, source, animation )
end
return speechId
end
function PlaySpeechCueFromSource( cue, source, useDefaultAnim, queue, useSubtitles, subtitleColor, args )
if PlayingTextLines and not args.AllowTalkOverTextLines then
return 0
end
if source == nil then
source = CurrentRun.Hero
end
if useDefaultAnim == nil then
useDefaultAnim = true
end
local anim = nil
if useDefaultAnim then
anim = StatusAnimations.Speaking
end
return PlaySpeechCue( cue, source, anim, queue, useSubtitles, subtitleColor, args )
end
function IsVoiceLineEligible( currentRun, line, prevLine, parentLine, source, args )
if line == nil then
return false
end
args = args or {}
if source ~= nil then
if line.RequiresFalseCharmed ~= nil and source.Charmed then
return false
end
if line.RequiresCharmed ~= nil and not source.Charmed then
return false
end
if line.RequiredSourceValue ~= nil and not source[line.RequiredSourceValue] then
return false
end
if line.RequiredSourceValueFalse ~= nil and source[line.RequiredSourceValueFalse] then
return false
end
end
if args ~= nil and args.ElapsedTime ~= nil then
if line.RequiredMinElapsedTime ~= nil and args.ElapsedTime < line.RequiredMinElapsedTime then
return false
end
if line.RequiredMaxElapsedTime ~= nil and args.ElapsedTime > line.RequiredMaxElapsedTime then
return false
end
end
if line.ExplicitRequirements or ( parentLine ~= nil and parentLine.ExplicitRequirements ) then
if line.GameStateRequirements ~= nil and not IsGameStateEligible( currentRun, line, line.GameStateRequirements ) then
return false
end
else
if not IsGameStateEligible( currentRun, line, line.GameStateRequirements ) then
return false
end
end
if line.PlayOnceFromTableThisRun then
for k, subLine in ipairs( line ) do
if subLine.Cue ~= nil and currentRun.SpeechRecord[subLine.Cue] then
return false
end
end
end
if line.SuccessiveChanceToPlay ~= nil then
local anyLinePlayed = false
for k, subLine in ipairs( line ) do
if subLine.Cue ~= nil and SpeechRecord[subLine.Cue] then
anyLinePlayed = true
break
end
end
if anyLinePlayed and not RandomChance( line.SuccessiveChanceToPlay ) then
return false
end
end
if line.SuccessiveChanceToPlayAll ~= nil then
local allLinesPlayed = true
for k, subLine in ipairs( line ) do
if subLine.Cue ~= nil and not SpeechRecord[subLine.Cue] then
allLinesPlayed = false
break
end
end
if allLinesPlayed and not RandomChance( line.SuccessiveChanceToPlayAll ) then
return false
end
end
if line.Cue ~= nil then
if line.PlayOnce or ( parentLine ~= nil and parentLine.PlayOnce ) then
if args.PlayOnceContext ~= nil then
DebugPrint({ Text = "checking context = "..tostring(args.PlayOnceContext) })
GameState.SpeechRecordContexts[args.PlayOnceContext] = GameState.SpeechRecordContexts[args.PlayOnceContext] or {}
if GameState.SpeechRecordContexts[args.PlayOnceContext][line.Cue] then
return false
end
elseif SpeechRecord[line.Cue] then
return false
end
end
if line.PlayOnceThisRun or ( parentLine ~= nil and parentLine.PlayOnceThisRun ) then
if currentRun.SpeechRecord[line.Cue] then
return false
end
end
local chanceToPlayAgain = line.ChanceToPlayAgain or ( parentLine ~= nil and parentLine.ChanceToPlayAgain )
if chanceToPlayAgain and SpeechRecord[line.Cue] and not RandomChance( chanceToPlayAgain ) then
return false
end
if line.CooldownTime ~= nil then
if not CheckCooldown( line.CooldownName or line.Cue, line.CooldownTime ) then
--DebugPrint({ Text = "VO blocked from cooldown: "..tostring(line.CooldownName or line.Cue) })
return false
end
end
if line.Cooldowns ~= nil then
for k, cooldown in pairs( line.Cooldowns ) do
local cooldownTime = cooldown.Time or line.CooldownTime
if cooldownTime == nil and source ~= nil then
cooldownTime = source.SpeechCooldownTime
end
if not CheckCooldown( cooldown.Name, cooldownTime ) then
--DebugPrint({ Text = "VO blocked from cooldown: "..tostring(cooldown.Name) })
return false
end
end
end
else
if line.CooldownTime ~= nil then
local cooldownName = line.CooldownName
if cooldownName == nil and line[1] ~= nil then
cooldownName = line[1].CooldownName or line[1].Cue
end
if cooldownName ~= nil and not CheckCooldown( cooldownName, line.CooldownTime ) then
--DebugPrint({ Text = "VO blocked from cooldown: "..tostring(cooldownName) })
return false
end
end
if line.Cooldowns ~= nil then
for k, cooldown in pairs( line.Cooldowns ) do
local cooldownTime = cooldown.Time or line.CooldownTime
if cooldownTime == nil and source ~= nil then
cooldownTime = source.SpeechCooldownTime
end
if not CheckCooldown( cooldown.Name, cooldownTime ) then
--DebugPrint({ Text = "VO blocked from cooldown: "..tostring(cooldown.Name) })
return false
end
end
end
end
if line.RequiresSourceAlive then
if line.ObjectType ~= nil then
local typeIds = GetIdsByType({ Name = line.ObjectType })
local objectId = typeIds[1]
source = ActiveEnemies[objectId]
end
if source == nil or source.IsDead then
return false
end
end
return true
end
function SometimesPlayCombatResolvedVoiceLines()
if not RandomChance( 0.25 ) then
return
end
local currentHealthFraction = CurrentRun.Hero.Health / CurrentRun.Hero.MaxHealth
if currentHealthFraction >= 0.95 then
thread( PlayVoiceLines, GlobalVoiceLines.CombatResolvedHighHealthLines, true )
elseif currentHealthFraction >= 0.3 and currentHealthFraction < 0.95 then
thread( PlayVoiceLines, GlobalVoiceLines.CombatResolvedMediumHealthLines, true )
elseif currentHealthFraction < 0.3 then
thread( PlayVoiceLines, GlobalVoiceLines.CombatResolvedLowHealthLines, true )
end
end
function AmbientChatting( source, args )
-- play a random line from ChattingRepeatable or similar table, from the character source position, with speech bubble; when player gets sufficiently close, play an 'interrupt' line, interrupting the ambient line if it's still playing
if args.StartDistance ~= nil then
local notifyName = "StartDistance"..source.Name
NotifyWithinDistanceAny({ Ids = { CurrentRun.Hero.ObjectId }, DestinationIds = { source.ObjectId }, Distance = args.StartDistance, Notify = notifyName })
waitUntil( notifyName )
end
thread( CheckDistanceTrigger, args.DistanceTrigger, source )
PlayVoiceLines( args.VoiceLines, nil, source )
-- source.BlockStatusAnimations = true
wait( 1.5 )
-- source.BlockStatusAnimations = false
end
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。