Moderator elections are being held. See Valve Developer Community:Moderator elections for more details.
Users who would like to run for moderator must be autoconfirmed and have at least 100 edits. Users can check their own edit count at Special:Preferences.

L4D2 Level Design/Custom Finale/zh

From Valve Developer Community
Jump to: navigation, search

本页面由大康翻译于2022年1月2日。部分内容由机器翻译。


求生之路2 自定义结局会自动从包含阶段列表的脚本(VScript)中读取:<地图名称>_finale.nut。该系统具有在满足任意条件(即终极感染者、Aztec tomb puzzles、feats of strength 等)时增强结局阶段的能力。一旦所有阶段都完成,trigger_finale 会触发 FinaleEscapeStarted 输出。

自定义结局地图包括 c2m5_concert、c3m4_plantation、c4m5_milltown_escape、c7m3_port,假设求生之路1的结局也一样(毫不留情已被确认是使用自定义结局的)。官方地图不使用Standard(标准) 结局类型。

组成内容

地图

最起码,所有需要改变的是 trigger_finale 的 Finale Type (结局类型),从StandardCustom。你还应该考虑其他选项和细节:

  • 除非通过脚本 EntFire(直接或间接)或简单地在游戏中使用 I/O 为导演提供输入 EndCustomScriptedStage,否则猛攻(Onslaught)阶段类型不会结束。
  • info_director: 在自定义结局事件时,OnCustomPanicStageFinished, OnPanicEventFinished (也许只是为了渐强事件),和 OnUserDefinedScriptEvent(1-4) 输出可用,链接到 vscript 阶段状态或方法,如 .UserDefinedEvent1()-.UserDefinedEvent4().
  • trigger_finale: AdvanceFinaleState 输入可用。

脚本

正如 L4D2 脚本文章中所讨论的,有四种阶段类型、附加的自定义结局特定导演选项和可用的特殊功能。

以下是带有附加注释的自定义结局脚本:

c2m5_concert_finale.nut

//-----------------------------------------------------------------------------
// 阶段类型的列举

ERROR <- -1 //这个枚举并不总是使用,但到底是什么!
PANIC <- 0
TANK <- 1
DELAY <- 2
ONSLAUGHT <- 3

//-----------------------------------------------------------------------------
// 将提供给 DirectorOptions 的表的初始化

SharedOptions <-
{
	A_CustomFinale_StageCount = 9 //阶段数。也被导演VS评分用了??
	
 	A_CustomFinale1 = PANIC
	A_CustomFinaleValue1 = 1 //1 个尸潮
	
 	A_CustomFinale2 = PANIC
	A_CustomFinaleValue2 = 1

	A_CustomFinale3 = DELAY
	A_CustomFinaleValue3 = 15 //15秒的延迟

	A_CustomFinale4 = TANK
	A_CustomFinaleValue4 = 1 //1 TANK
	A_CustomFinaleMusic4 = "" //自定义音乐条目在脚本中关闭,在游戏中播放

	A_CustomFinale5 = DELAY
	A_CustomFinaleValue5 = 15

	A_CustomFinale6 = PANIC
	A_CustomFinaleValue6 = 2

	A_CustomFinale7 = DELAY
	A_CustomFinaleValue7 = 10

	A_CustomFinale8 = TANK
	A_CustomFinaleValue8 = 1
	A_CustomFinaleMusic8 = ""

	A_CustomFinale9 = DELAY
	A_CustomFinaleValue9 = RandomInt( 5, 10 ) //5-10 秒之间的随机延迟
	
        // 其他导演选项
	PreferredMobDirection = SPAWN_LARGE_VOLUME
	PreferredSpecialDirection = SPAWN_LARGE_VOLUME
	ShouldConstrainLargeVolumeSpawn = false

	ZombieSpawnRange = 3000
	
	SpecialRespawnInterval = 20
} 

InitialPanicOptions <- //与阶段 1 的 SharedOptions 分开的表
{
	ShouldConstrainLargeVolumeSpawn = true
}


PanicOptions <- //一般的尸潮选项
{
	CommonLimit = 25
}

TankOptions <- //TANK 阶段时使用的另一个单独的表
{
	ShouldAllowSpecialsWithTank = true
	SpecialRespawnInterval = 30
}


DirectorOptions <- clone SharedOptions //DirectorOptions 使用 SharedOptions
{
}

//-----------------------------------------------------------------------------
// 经常用于将表复制到另一个表

function AddTableToTable( dest, src )
{
	foreach( key, val in src )
	{
		dest[key] <- val
	}
}

//-----------------------------------------------------------------------------
// 使用自定义逻辑操作 DirectorOptions
// 在这种情况下,DirectorOptions 仅在新阶段开始时更改

function OnBeginCustomFinaleStage( num, type ) //特殊功能,当每个新的结局阶段开始时
{
	if ( developer() > 0 ) //如果开发者模式打开,-dev
	{
		printl("========================================================");
		printl( "Beginning custom finale stage " + num + " of type " + type );
	}

        //设置/确定 WAVEOPTIONS
	local waveOptions = null
	if ( num == 1 ) //如果第一阶段(假设是 PANIC)
	{
		waveOptions = InitialPanicOptions
	}
	else if ( type == PANIC ) //一般的尸潮(又名“恐慌事件”,"Panic Event")事件
	{
		waveOptions = PanicOptions

                /* Change MegaMobSize if MegaMobMinSize is available in PanicOptions is available.
                   Was this ever used?? */
		if ( "MegaMobMinSize" in PanicOptions )
		{
			waveOptions.MegaMobSize <- RandomInt( PanicOptions.MegaMobMinSize, MegaMobMaxSize )
		}
	}
	else if ( type == TANK ) //TANK 时间!
	{
		waveOptions = TankOptions
	}
	
	//---------------------------------
        // 完成确定 WAVEOPTIONS。现在,实际转到 DirectorOptions

	MapScript.DirectorOptions.clear() //清除所有 DirectorOptions 表的值

	AddTableToTable( MapScript.DirectorOptions, SharedOptions ); //返回到 SharedOptions

	if ( waveOptions != null ) //最后添加依赖于阶段的选项(WAVEOPTIONS)
	{
		AddTableToTable( MapScript.DirectorOptions, waveOptions );
	}

	//---------------------------------

	if ( developer() > 0 ) //更多开发输出 (-dev)
	{
		Msg( "\n*****\nMapScript.DirectorOptions:\n" );
		foreach( key, value in MapScript.DirectorOptions )
		{
			Msg( "    " + key + " = " + value + "\n" );
		}

		if ( LocalScript.rawin( "DirectorOptions" ) ) //RAWIN 检查 DirectorOptions 是否存在
		{
			Msg( "\n*****\nLocalScript.DirectorOptions:\n" );
			foreach( key, value in LocalScript.DirectorOptions )
			{
				Msg( "    " + key + " = " + value + "\n" );
			}
		}
		printl("========================================================");
	}
}

牺牲结局

牺牲结局基于自定义结局,并在牺牲更新中引入了额外的硬编码修改。

Blank image.png待完善: 分解、解释、指导

c7m3_port_finale.nut

这比其他自定义结局更复杂,但本质上是为了自定义体验。该脚本表明牺牲结局类型主要取决于新的游戏内实体和实体功能。

//-----------------------------------------------------
// This script handles the logic for the Port / Bridge
// finale in the River Campaign. 
//
//-----------------------------------------------------
Msg("Initiating c7m3_port_finale script\n");

//-----------------------------------------------------
// Enumerations
ERROR		<- -1
PANIC 		<- 0
TANK 		<- 1
DELAY 		<- 2

//-----------------------------------------------------

// This keeps track of the number of times the generator button has been pressed. 
// Init to 1, since one button press has been used to start the finale and run 
// this script. 
ButtonPressCount <- 1

// This stores the stage number that we last
// played the "Press the Button!" VO
LastVOButtonStageNumber <- 0

// We use this to keep from running a bunch of queued advances really quickly. 
// Init to true because we are starting a finale from a button press in the pre-finale script 
// see GeneratorButtonPressed in c7m3_port.nut
PendingWaitAdvance <- true	

// We use three generator button presses to push through
// 8 stages. We have to queue up state advances
// depending on the state of the finale when buttons are pressed
QueuedDelayAdvances <- 0


// Tracking current finale states
CurrentFinaleStageNumber <- ERROR
CurrentFinaleStageType <- ERROR

// The finale is 3 phases. 
// We randomize the event types in the first two
local RandomFinaleStage1 = 0
local RandomFinaleStage2 = 0
local RandomFinaleStage4 = 0
local RandomFinaleStage5 = 0

// PHASE 1 EVENTS
if ( RandomInt( 1, 100 ) < 50 )
{
	RandomFinaleStage1 = PANIC
	RandomFinaleStage2 = TANK
}
else
{
	RandomFinaleStage1 = TANK
	RandomFinaleStage2 = PANIC
}


// PHASE 2 EVENTS
if ( RandomInt( 1, 100 ) < 50 )
{
	RandomFinaleStage4 = PANIC
	RandomFinaleStage5 = TANK
}
else
{
	RandomFinaleStage4 = TANK
	RandomFinaleStage5 = PANIC
}



// We want to give the survivors a little of extra time to 
// get on their feet before the escape, since you have to fight through 
// the sacrifice.

PreEscapeDelay <- 0
if ( Director.GetGameMode() == "coop" )
{
	PreEscapeDelay <- 5
}
else if ( Director.GetGameMode() == "versus" )
{
	PreEscapeDelay <- 15
}

DirectorOptions <-
{	
	 
	A_CustomFinale_StageCount = 8
	 
	// PHASE 1
	A_CustomFinale1 = RandomFinaleStage1
	A_CustomFinaleValue1 = 1
	A_CustomFinale2 = RandomFinaleStage2
	A_CustomFinaleValue2 = 1
	A_CustomFinale3 = DELAY
	A_CustomFinaleValue3 = 9999
	
	
	// PHASE 2
	A_CustomFinale4 = RandomFinaleStage4
	A_CustomFinaleValue4 = 1
	A_CustomFinale5 = RandomFinaleStage5
	A_CustomFinaleValue5 = 1	
	A_CustomFinale6 = DELAY
	A_CustomFinaleValue6 = 9999 	 
	
	
	// PHASE 3
	A_CustomFinale7 = TANK
	A_CustomFinaleValue7 = 1	 	 		 
	A_CustomFinale8 = DELAY
	A_CustomFinaleValue8 = PreEscapeDelay
	 
	 
	 
	TankLimit = 4
	WitchLimit = 0
	CommonLimit = 20	
	HordeEscapeCommonLimit = 15	
	EscapeSpawnTanks = false
	//SpecialRespawnInterval = 80

}


function OnBeginCustomFinaleStage( num, type )
{
	printl( "*!* Beginning custom finale stage " + num + " of type " + type );
	printl( "*!* PendingWaitAdvance " + PendingWaitAdvance + ", QueuedDelayAdvances " + QueuedDelayAdvances );
	
	// Store off the state... 
	CurrentFinaleStageNumber = num
	CurrentFinaleStageType = type
	
	// Acknowledge the state advance
	PendingWaitAdvance = false
}


function GeneratorButtonPressed()
{
    printl( "*!* GeneratorButtonPressed finale stage " + CurrentFinaleStageNumber + " of type " +CurrentFinaleStageType );
	printl( "*!* PendingWaitAdvance " + PendingWaitAdvance + ", QueuedDelayAdvances " + QueuedDelayAdvances );
	
	
	ButtonPressCount++
	
	
	local ImmediateAdvances = 0
	
	
	if ( CurrentFinaleStageNumber == 1 || CurrentFinaleStageNumber == 4 )
	{		
		// First stage of a phase, so next stage is an "action" stage too.
		// Advance to next action stage, and then queue an advance to the 
		// next delay.
		QueuedDelayAdvances++
		ImmediateAdvances = 1
	}
	else if ( CurrentFinaleStageNumber == 2 || CurrentFinaleStageNumber == 5 )
	{
		// Second stage of a phase, so next stage is a "delay" stage.
		// We need to immediately advance past the delay and into an action state. 
		
		//QueuedDelayAdvances++	// NOPE!
		ImmediateAdvances = 2
	}
	else if ( CurrentFinaleStageNumber == 3 || CurrentFinaleStageNumber == 6 )
	{
		// Wait states... (very long delay)
		// Advance immediately into an action state
		
		//QueuedDelayAdvances++
		ImmediateAdvances = 1
	}
	else if ( CurrentFinaleStageNumber == -1 || 
              CurrentFinaleStageNumber == 0 )
	{
		// the finale is *just* about to start... 
		// we can get this if all the buttons are hit at once at the beginning
		// Just queue a wait advance
		QueuedDelayAdvances++
		ImmediateAdvances = 0
	}
	else
	{
		printl( "*!* Unhandled generator button press! " );
	}

	if ( ImmediateAdvances > 0 )
	{	
		EntFire( "generator_start_model", "Enable" )
		
		
		if ( ImmediateAdvances == 1 )
		{
			printl( "*!* GeneratorButtonPressed Advancing State ONCE");
			EntFire( "generator_start_model", "AdvanceFinaleState" )
		}
		else if ( ImmediateAdvances == 2 )
		{
			printl( "*!* GeneratorButtonPressed Advancing State TWICE");
			EntFire( "generator_start_model", "AdvanceFinaleState" )
			EntFire( "generator_start_model", "AdvanceFinaleState" )
		}
		
		EntFire( "generator_start_model", "Disable" )
		
		PendingWaitAdvance = true
	}
	
}

function Update() //Called every 0.100 seconds
{
	// Should we advance the finale state?
	// 1. If we are in a DELAY state
	// 2. And we have queued advances.... 
	// 3. And we have not just tried to advance the advance the state.... 
	if ( CurrentFinaleStageType == DELAY && QueuedDelayAdvances > 0 && !PendingWaitAdvance )
	{
		// If things are calm (relatively), jump to the next state
		if ( !Director.IsTankInPlay() && !Director.IsAnySurvivorInCombat() )
		{
			if ( Director.GetPendingMobCount() < 1 && Director.GetCommonInfectedCount() < 5 )
			{
				printl( "*!* Update Advancing State finale stage " + CurrentFinaleStageNumber + " of type " +CurrentFinaleStageType );
				printl( "*!* PendingWaitAdvance " + PendingWaitAdvance + ", QueuedDelayAdvances " + QueuedDelayAdvances );
		
				QueuedDelayAdvances--
				EntFire( "generator_start_model", "Enable" )
				EntFire( "generator_start_model", "AdvanceFinaleState" )
				EntFire( "generator_start_model", "Disable" )
				PendingWaitAdvance = true
			}
		}
	}
	
	// Should we fire the director event to play the "Press the button!" Nag VO?	
	// If we are on an infinite delay stage...
	if ( CurrentFinaleStageType == DELAY && CurrentFinaleStageNumber > 1 && CurrentFinaleStageNumber < 7 )	
	{		
		// 1. We have not nagged for this stage yet
		// 2. There are button presses remaining
		if ( CurrentFinaleStageNumber != LastVOButtonStageNumber && ButtonPressCount < 3 )
		{
			// We are not about to process a wait advance..
			if ( QueuedDelayAdvances == 0 && !PendingWaitAdvance )
			{
				// If things are pretty calm, run the event
				if ( Director.GetPendingMobCount() < 1 && Director.GetCommonInfectedCount() < 1 )
				{
					if ( !Director.IsTankInPlay() && !Director.IsAnySurvivorInCombat() )
					{
						printl( "*!* Update firing event 1 (VO Prompt)" )
						LastVOButtonStageNumber = CurrentFinaleStageNumber
						Director.UserDefinedEvent1()
					}
				}
			}
		}
	}
	
}


function EnableEscapeTanks()
// This is called in-game via logic_relay at info_director
// enable the escape tanks at a different time.
// Input: Runscriptcode
// Parm: DirectorScript.MapScript.LocalScript.EnableEscapeTanks()
{
	printl( "*!* EnableEscapeTanks finale stage " + CurrentFinaleStageNumber + " of type " +CurrentFinaleStageType );
	
	//Msg( "\n*****\nMapScript.DirectorOptions:\n" );
	//foreach( key, value in MapScript.DirectorOptions )
	//{
	//	Msg( "    " + key + " = " + value + "\n" );
	//}

	MapScript.DirectorOptions.EscapeSpawnTanks <- true
}

解密的官方结局脚本

这是所有的结局脚本,于 2021 年 8 月 2 日解密。
这些尚未修改以提供任何附加信息,某些部分可能会造成混淆。

c1m4_atrium_finale.nuc

Msg("----------------------FINALE SCRIPT------------------\n")
//-----------------------------------------------------
PANIC <- 0
TANK <- 1
DELAY <- 2
ONSLAUGHT <- 3
//-----------------------------------------------------

SharedOptions <-
{
 	A_CustomFinale1 = ONSLAUGHT
	A_CustomFinaleValue1 = ""

	A_CustomFinale2 = PANIC
	A_CustomFinaleValue2 = 1

	A_CustomFinale3 = ONSLAUGHT
	A_CustomFinaleValue3 = "c1m4_delay"
	
	A_CustomFinale4 = PANIC
	A_CustomFinaleValue4 = 1

	A_CustomFinale5 = ONSLAUGHT
	A_CustomFinaleValue5 = "c1m4_delay"

	A_CustomFinale6 = TANK
	A_CustomFinaleValue6 = 1

	A_CustomFinale7 = ONSLAUGHT
	A_CustomFinaleValue7 = "c1m4_delay"
 
 	A_CustomFinale8 = PANIC
	A_CustomFinaleValue8 = 1

	A_CustomFinale9 = ONSLAUGHT
	A_CustomFinaleValue9 = "c1m4_delay"
 
 	A_CustomFinale10 = PANIC
	A_CustomFinaleValue10 = 1

	A_CustomFinale11 = ONSLAUGHT
	A_CustomFinaleValue11 = "c1m4_delay"

	A_CustomFinale12 = PANIC
	A_CustomFinaleValue12 = 1
	
 	A_CustomFinale13 = ONSLAUGHT
	A_CustomFinaleValue13 = "c1m4_delay"
	
	A_CustomFinale14 = TANK
	A_CustomFinaleValue14 = 1   
	
 	A_CustomFinale15 = ONSLAUGHT
	A_CustomFinaleValue15 = "c1m4_delay"
	
	A_CustomFinale16 = PANIC
	A_CustomFinaleValue16 = 1  
	
 	A_CustomFinale17 = ONSLAUGHT
	A_CustomFinaleValue17 = "c1m4_delay"    
	
 	A_CustomFinale18 = PANIC
	A_CustomFinaleValue18 = 1  
	
 	A_CustomFinale19 = ONSLAUGHT
	A_CustomFinaleValue19 = "c1m4_delay"
	
	A_CustomFinale20 = PANIC
	A_CustomFinaleValue20 = 1   
	
 	A_CustomFinale21 = ONSLAUGHT
	A_CustomFinaleValue21 = "c1m4_delay"
	
	A_CustomFinale22 = TANK
	A_CustomFinaleValue22 = 1  
	
 	A_CustomFinale23 = ONSLAUGHT
	A_CustomFinaleValue23 = "c1m4_delay"    
	
 	A_CustomFinale24 = PANIC
	A_CustomFinaleValue24 = 1
	
 	A_CustomFinale25 = ONSLAUGHT
	A_CustomFinaleValue25 = "c1m4_delay"
	
	A_CustomFinale26 = PANIC
	A_CustomFinaleValue26 = 1   
	
 	A_CustomFinale27 = ONSLAUGHT
	A_CustomFinaleValue27 = "c1m4_delay"
	
	A_CustomFinale28 = PANIC
	A_CustomFinaleValue28 = 1  
	
 	A_CustomFinale29 = ONSLAUGHT
	A_CustomFinaleValue29 = "c1m4_delay"    
	
 	A_CustomFinale30 = PANIC
	A_CustomFinaleValue30 = 1

 	A_CustomFinale31 = ONSLAUGHT
	A_CustomFinaleValue31 = "c1m4_delay"   
	
	//-----------------------------------------------------

	PreferredMobDirection = SPAWN_LARGE_VOLUME
	PreferredSpecialDirection = SPAWN_LARGE_VOLUME
	
//	BoomerLimit = 0
//	SmokerLimit = 2
//	HunterLimit = 1
//	SpitterLimit = 1
//	JockeyLimit = 0
//	ChargerLimit = 1

	ProhibitBosses = true
	ZombieSpawnRange = 3000
	MobRechargeRate = 0.5
	HordeEscapeCommonLimit = 15
	BileMobSize = 15
	
	MusicDynamicMobSpawnSize = 8
	MusicDynamicMobStopSize = 2
	MusicDynamicMobScanStopSize = 1
} 

InitialOnslaughtOptions <-
{
    LockTempo = 0
	IntensityRelaxThreshold = 1.1
	RelaxMinInterval = 2
	RelaxMaxInterval = 4
	SustainPeakMinTime = 25
	SustainPeakMaxTime = 30
	
	MobSpawnMinTime = 4
	MobSpawnMaxTime = 8
	MobMinSize = 2
	MobMaxSize = 6
	CommonLimit = 5
	
	SpecialRespawnInterval = 100
}

PanicOptions <-
{
	MegaMobSize = 0 // randomized in OnBeginCustomFinaleStage
	MegaMobMinSize = 20
	MegaMobMaxSize = 40
	
	CommonLimit = 15
	
	SpecialRespawnInterval = 40
}

TankOptions <-
{
	ShouldAllowMobsWithTank = true
	ShouldAllowSpecialsWithTank = true

	MobSpawnMinTime = 10
	MobSpawnMaxTime = 20
	MobMinSize = 3
	MobMaxSize = 5

	CommonLimit = 7
	
	SpecialRespawnInterval = 60
}


DirectorOptions <- clone SharedOptions
{
}


//-----------------------------------------------------

// number of cans needed to escape.
NumCansNeeded <- 13

// fewer cans in single player since bots don't help much
if ( Director.IsSinglePlayerGame() )
{
	NumCansNeeded <- 8
}

// duration of delay stage.
DelayMin <- 10
DelayMax <- 20

// Number of touches and/or pours allowed before a delay is aborted.
DelayPourThreshold <- 1
DelayTouchedOrPouredThreshold <- 2


// Once the delay is aborted, amount of time before it progresses to next stage.
AbortDelayMin <- 1
AbortDelayMax <- 3

// Number of touches and pours it takes to transition out of c1m4_finale_wave_1
GimmeThreshold <- 4


// console overrides
if ( Director.IsPlayingOnConsole() )
{
	DelayMin <- 20
	DelayMax <- 30
	
	// Number of touches and/or pours allowed before a delay is aborted.
	DelayPourThreshold <- 2
	DelayTouchedOrPouredThreshold <- 4
	
	TankOptions.ShouldAllowSpecialsWithTank = false
}
//-----------------------------------------------------
//      INIT
//-----------------------------------------------------

GasCansTouched          <- 0
GasCansPoured           <- 0
DelayTouchedOrPoured    <- 0
DelayPoured             <- 0

EntFire( "timer_delay_end", "LowerRandomBound", DelayMin )
EntFire( "timer_delay_end", "UpperRandomBound", DelayMax )
EntFire( "timer_delay_abort", "LowerRandomBound", AbortDelayMin )
EntFire( "timer_delay_abort", "UpperRandomBound", AbortDelayMax )

// this is too late. Moved to c1m4_atrium.nut
//EntFire( "progress_display", "SetTotalItems", NumCansNeeded )

function AbortDelay(){}  	// only defined during a delay, in c1m4_delay.nut
function EndDelay(){}		// only defined during a delay, in c1m4_delay.nut

NavMesh.UnblockRescueVehicleNav()

//-----------------------------------------------------

function GasCanTouched()
{
    GasCansTouched++
    Msg(" Touched: " + GasCansTouched + "\n")   
     
    EvalGasCansPouredOrTouched()
}

function GasCanPoured()
{
    GasCansPoured++
    DelayPoured++
    Msg(" Poured: " + GasCansPoured + "\n")   

    if ( GasCansPoured == NumCansNeeded )
    {
        Msg(" needed: " + NumCansNeeded + "\n") 
        EntFire( "relay_car_ready", "trigger" )
    }

    EvalGasCansPouredOrTouched()
}

function EvalGasCansPouredOrTouched()
{
    TouchedOrPoured <- GasCansPoured + GasCansTouched
    Msg(" Poured or touched: " + TouchedOrPoured + "\n")

    DelayTouchedOrPoured++
    Msg(" DelayTouchedOrPoured: " + DelayTouchedOrPoured + "\n")
    Msg(" DelayPoured: " + DelayPoured + "\n")
    
    if (( DelayTouchedOrPoured >= DelayTouchedOrPouredThreshold ) || ( DelayPoured >= DelayPourThreshold ))
    {
        AbortDelay()
    }
    
    switch( TouchedOrPoured )
    {
        case GimmeThreshold:
            EntFire( "@director", "EndCustomScriptedStage" )
            break
    }
}
//-----------------------------------------------------

function AddTableToTable( dest, src )
{
	foreach( key, val in src )
	{
		dest[key] <- val
	}
}

function OnBeginCustomFinaleStage( num, type )
{
	printl( "Beginning custom finale stage " + num + " of type " + type );
	
	local waveOptions = null
	if ( num == 1 )
	{
		waveOptions = InitialOnslaughtOptions
	}
	else if ( type == PANIC )
	{
		waveOptions = PanicOptions
		waveOptions.MegaMobSize = PanicOptions.MegaMobMinSize + rand()%( PanicOptions.MegaMobMaxSize - PanicOptions.MegaMobMinSize )
		
		Msg("*************************" + waveOptions.MegaMobSize + "\n")
		
	}
	else if ( type == TANK )
	{
		waveOptions = TankOptions
	}
	
	//---------------------------------


	MapScript.DirectorOptions.clear()
	

	AddTableToTable( MapScript.DirectorOptions, SharedOptions );

	if ( waveOptions != null )
	{
		AddTableToTable( MapScript.DirectorOptions, waveOptions );
	}
	
	
	Director.ResetMobTimer()
	
	if ( developer() > 0 )
	{
		Msg( "\n*****\nMapScript.DirectorOptions:\n" );
		foreach( key, value in MapScript.DirectorOptions )
		{
			Msg( "    " + key + " = " + value + "\n" );
		}

		if ( LocalScript.rawin( "DirectorOptions" ) )
		{
			Msg( "\n*****\nLocalScript.DirectorOptions:\n" );
			foreach( key, value in LocalScript.DirectorOptions )
			{
				Msg( "    " + key + " = " + value + "\n" );
			}
		}
	}
}

//-----------------------------------------------------


if ( Director.GetGameModeBase() == "versus" )
{
	SharedOptions.ProhibitBosses = false
}

c1m4_finale_wave_1.nuc

Msg("Atrium Finale Wave " + ( CurrentWave + 1 ) + "\n");

c1m4_delay.nuc

Msg("**Delay started**\n")

DirectorOptions <-
{
	MobMinSize = 2
	MobMaxSize = 3
        
	BoomerLimit = 0
	SmokerLimit = 0
	HunterLimit = 0
	SpitterLimit = 0
	JockeyLimit = 0
	ChargerLimit = 0
        
	MinimumStageTime = 15
       
	CommonLimit = 5
}

Director.ResetMobTimer()


// start the delay timer
EntFire( "timer_delay_end", "enable" )

//reset
DelayTouchedOrPoured   <- 0
DelayPoured            <- 0
//-------------------------------------------------


// abort the delay if a survivor picks up or pours a gas can
function AbortDelay()
{
    Msg("**Delay aborted early**\n")    
    EntFire( "timer_delay_abort", "enable" )
}

function EndDelay()
{
        Msg("**Delay ended**\n") 
        EntFire( "timer_delay_end", "Disable" )
        EntFire( "timer_delay_end", "ResetTimer" )
        EntFire( "timer_delay_abort", "Disable" )
        EntFire( "timer_delay_abort", "ResetTimer" )
        EntFire( "@director", "EndCustomScriptedStage" )
}

c2m5_concert_finale.nuc

//-----------------------------------------------------------------------------

PANIC <- 0
TANK <- 1
DELAY <- 2
ONSLAUGHT <- 3

//-----------------------------------------------------------------------------

SharedOptions <-
{
	A_CustomFinale_StageCount = 9
	
 	A_CustomFinale1 = PANIC
	A_CustomFinaleValue1 = 1
	
 	A_CustomFinale2 = PANIC
	A_CustomFinaleValue2 = 1

	A_CustomFinale3 = DELAY
	A_CustomFinaleValue3 = 15

	A_CustomFinale4 = TANK
	A_CustomFinaleValue4 = 1
	A_CustomFinaleMusic4 = ""

	A_CustomFinale5 = DELAY
	A_CustomFinaleValue5 = 15

	A_CustomFinale6 = PANIC
	A_CustomFinaleValue6 = 2

	A_CustomFinale7 = DELAY
	A_CustomFinaleValue7 = 10

	A_CustomFinale8 = TANK
	A_CustomFinaleValue8 = 1
	A_CustomFinaleMusic8 = ""

	A_CustomFinale9 = DELAY
	A_CustomFinaleValue9 = RandomInt( 5, 10 )
	
	PreferredMobDirection = SPAWN_LARGE_VOLUME
	PreferredSpecialDirection = SPAWN_LARGE_VOLUME
	ShouldConstrainLargeVolumeSpawn = false

	ZombieSpawnRange = 3000
	
	SpecialRespawnInterval = 20
} 

InitialPanicOptions <-
{
	ShouldConstrainLargeVolumeSpawn = true
}


PanicOptions <-
{
	CommonLimit = 25
}

TankOptions <-
{
	ShouldAllowSpecialsWithTank = true
	SpecialRespawnInterval = 30
}


DirectorOptions <- clone SharedOptions
{
}

//-----------------------------------------------------------------------------

function AddTableToTable( dest, src )
{
	foreach( key, val in src )
	{
		dest[key] <- val
	}
}

//-----------------------------------------------------------------------------

function OnBeginCustomFinaleStage( num, type )
{
	if ( developer() > 0 )
	{
		printl("========================================================");
		printl( "Beginning custom finale stage " + num + " of type " + type );
	}

	local waveOptions = null
	if ( num == 1 )
	{
		waveOptions = InitialPanicOptions
	}
	else if ( type == PANIC )
	{
		waveOptions = PanicOptions
		if ( "MegaMobMinSize" in PanicOptions )
		{
			waveOptions.MegaMobSize <- RandomInt( PanicOptions.MegaMobMinSize, MegaMobMaxSize )
		}
	}
	else if ( type == TANK )
	{
		waveOptions = TankOptions
	}
	
	//---------------------------------

	MapScript.DirectorOptions.clear()

	AddTableToTable( MapScript.DirectorOptions, SharedOptions );

	if ( waveOptions != null )
	{
		AddTableToTable( MapScript.DirectorOptions, waveOptions );
	}

	//---------------------------------

	if ( developer() > 0 )
	{
		Msg( "\n*****\nMapScript.DirectorOptions:\n" );
		foreach( key, value in MapScript.DirectorOptions )
		{
			Msg( "    " + key + " = " + value + "\n" );
		}

		if ( LocalScript.rawin( "DirectorOptions" ) )
		{
			Msg( "\n*****\nLocalScript.DirectorOptions:\n" );
			foreach( key, value in LocalScript.DirectorOptions )
			{
				Msg( "    " + key + " = " + value + "\n" );
			}
		}
		printl("========================================================");
	}
}

c3m4_plantation_finale.nuc

//-----------------------------------------------------
local PANIC = 0
local TANK = 1
local DELAY = 2
//-----------------------------------------------------

DirectorOptions <-
{
	//-----------------------------------------------------

	 A_CustomFinale_StageCount = 8
	 
	 A_CustomFinale1 = PANIC
	 A_CustomFinaleValue1 = 2
	 
	 A_CustomFinale2 = DELAY
	 A_CustomFinaleValue2 = 12
	 
	 A_CustomFinale3 = TANK
	 A_CustomFinaleValue3 = 1
	 
	 A_CustomFinale4 = DELAY
	 A_CustomFinaleValue4 = 12
	 
	 A_CustomFinale5 = PANIC
	 A_CustomFinaleValue5 = 2
	 
	 A_CustomFinale6 = DELAY
	 A_CustomFinaleValue6 = 15
	 
	 A_CustomFinale7 = TANK
	 A_CustomFinaleValue7 = 2

	 A_CustomFinale8 = DELAY
	 A_CustomFinaleValue8 = 10
	 
SpecialRespawnInterval = 55

	//-----------------------------------------------------
}

c4m5_milltown_escape_finale.nuc

//-----------------------------------------------------
local PANIC = 0
local TANK = 1
local DELAY = 2
//-----------------------------------------------------

// default finale patten - for reference only

/*
CustomFinale1 <- PANIC
CustomFinaleValue1 <- 2

CustomFinale2 <- DELAY
CustomFinaleValue2 <- 10

CustomFinale3 <- TANK
CustomFinaleValue3 <- 1

CustomFinale4 <- DELAY
CustomFinaleValue4 <- 10

CustomFinale5 <- PANIC
CustomFinaleValue5 <- 2

CustomFinale6 <- DELAY
CustomFinaleValue6 <- 10

CustomFinale7 <- TANK
CustomFinaleValue7 <- 1

CustomFinale8 <- DELAY
CustomFinaleValue8 <- 2
*/

DirectorOptions <-
{
	//-----------------------------------------------------

	// 3 waves of mobs in between tanks

	 A_CustomFinale_StageCount = 8
	 
	 A_CustomFinale1 = PANIC
	 A_CustomFinaleValue1 = 1
	 
	 A_CustomFinale2 = DELAY
	 A_CustomFinaleValue2 = 10
	 
	 A_CustomFinale3 = TANK
	 A_CustomFinaleValue3 = 1
	 
	 A_CustomFinale4 = DELAY
	 A_CustomFinaleValue4 = 10
	 
	 A_CustomFinale5 = PANIC
	 A_CustomFinaleValue5 = 1
	 
	 A_CustomFinale6 = DELAY
	 A_CustomFinaleValue6 = 10
	 
	 A_CustomFinale7 = TANK
	 A_CustomFinaleValue7 = 1
	 
	 A_CustomFinale8 = DELAY
	 A_CustomFinaleValue8 = 15
	 
	 
	HordeEscapeCommonLimit = 15
	CommonLimit = 20
	SpecialRespawnInterval = 80


}

if ( "DirectorOptions" in LocalScript && "ProhibitBosses" in LocalScript.DirectorOptions )
{
	delete LocalScript.DirectorOptions.ProhibitBosses
}

/*
*/

c6m3_port_finale.nuc

Msg("----------------------FINALE SCRIPT------------------\n")
//-----------------------------------------------------
PANIC <- 0
TANK <- 1
DELAY <- 2
ONSLAUGHT <- 3
//-----------------------------------------------------

SharedOptions <-
{
 	A_CustomFinale1 = ONSLAUGHT
	A_CustomFinaleValue1 = ""

	A_CustomFinale2 = PANIC
	A_CustomFinaleValue2 = 1

	A_CustomFinale3 = ONSLAUGHT
	A_CustomFinaleValue3 = "c1m4_delay"
        
	A_CustomFinale4 = PANIC
	A_CustomFinaleValue4 = 1

	A_CustomFinale5 = ONSLAUGHT
	A_CustomFinaleValue5 = "c1m4_delay"

	A_CustomFinale6 = TANK
	A_CustomFinaleValue6 = 1

	A_CustomFinale7 = ONSLAUGHT
	A_CustomFinaleValue7 = "c1m4_delay"
 
 	A_CustomFinale8 = PANIC
	A_CustomFinaleValue8 = 1

	A_CustomFinale9 = ONSLAUGHT
	A_CustomFinaleValue9 = "c1m4_delay"
 
 	A_CustomFinale10 = PANIC
	A_CustomFinaleValue10 = 1

	A_CustomFinale11 = ONSLAUGHT
	A_CustomFinaleValue11 = "c1m4_delay"

	A_CustomFinale12 = PANIC
	A_CustomFinaleValue12 = 1
        
 	A_CustomFinale13 = ONSLAUGHT
	A_CustomFinaleValue13 = "c1m4_delay"
        
	A_CustomFinale14 = TANK
	A_CustomFinaleValue14 = 2   
        
 	A_CustomFinale15 = ONSLAUGHT
	A_CustomFinaleValue15 = "c1m4_delay"
        
	A_CustomFinale16 = PANIC
	A_CustomFinaleValue16 = 1  
                  
 	A_CustomFinale17 = ONSLAUGHT
	A_CustomFinaleValue17 = "c1m4_delay"    
                       
 	A_CustomFinale18 = PANIC
	A_CustomFinaleValue18 = 1  
	
 	A_CustomFinale19 = ONSLAUGHT
	A_CustomFinaleValue19 = "c1m4_delay"
        
	A_CustomFinale20 = PANIC
	A_CustomFinaleValue20 = 1   
        
 	A_CustomFinale21 = ONSLAUGHT
	A_CustomFinaleValue21 = "c1m4_delay"
        
	A_CustomFinale22 = TANK
	A_CustomFinaleValue22 = 1  
                  
 	A_CustomFinale23 = ONSLAUGHT
	A_CustomFinaleValue23 = "c1m4_delay"    
                       
 	A_CustomFinale24 = PANIC
	A_CustomFinaleValue24 = 1
															
 	A_CustomFinale25 = ONSLAUGHT
	A_CustomFinaleValue25 = "c1m4_delay"
        
	A_CustomFinale26 = PANIC
	A_CustomFinaleValue26 = 1   
        
 	A_CustomFinale27 = ONSLAUGHT
	A_CustomFinaleValue27 = "c1m4_delay"
        
	A_CustomFinale28 = PANIC
	A_CustomFinaleValue28 = 1  
                  
 	A_CustomFinale29 = ONSLAUGHT
	A_CustomFinaleValue29 = "c1m4_delay"    
                       
 	A_CustomFinale30 = PANIC
	A_CustomFinaleValue30 = 1

 	A_CustomFinale31 = ONSLAUGHT
	A_CustomFinaleValue31 = "c1m4_delay"   

	A_CustomFinale32 = TANK
	A_CustomFinaleValue32 = 2  
                  
 	A_CustomFinale33 = ONSLAUGHT
	A_CustomFinaleValue33 = "c1m4_delay"    
                       
 	A_CustomFinale34 = PANIC
	A_CustomFinaleValue34 = 1
															
 	A_CustomFinale35 = ONSLAUGHT
	A_CustomFinaleValue35 = "c1m4_delay"
        
	A_CustomFinale36 = PANIC
	A_CustomFinaleValue36 = 1   
        
 	A_CustomFinale37 = ONSLAUGHT
	A_CustomFinaleValue37 = "c1m4_delay"
        
	A_CustomFinale38 = PANIC
	A_CustomFinaleValue38 = 1  
                  
 	A_CustomFinale39 = ONSLAUGHT
	A_CustomFinaleValue39 = "c1m4_delay"    
                       
 	A_CustomFinale40 = PANIC
	A_CustomFinaleValue40 = 1

 	A_CustomFinale41 = ONSLAUGHT
	A_CustomFinaleValue41 = "c1m4_delay"   

	A_CustomFinale42 = TANK
	A_CustomFinaleValue42 = 1  
                  
 	A_CustomFinale43 = ONSLAUGHT
	A_CustomFinaleValue43 = "c1m4_delay"    
                       
 	A_CustomFinale44 = PANIC
	A_CustomFinaleValue44 = 1
															
 	A_CustomFinale45 = ONSLAUGHT
	A_CustomFinaleValue45 = "c1m4_delay"
        
	A_CustomFinale46 = PANIC
	A_CustomFinaleValue46 = 1   
        
 	A_CustomFinale47 = ONSLAUGHT
	A_CustomFinaleValue47 = "c1m4_delay"
        
	A_CustomFinale48 = PANIC
	A_CustomFinaleValue48 = 1  
                  
 	A_CustomFinale49 = ONSLAUGHT
	A_CustomFinaleValue49 = "c1m4_delay"    
                       
 	A_CustomFinale50 = PANIC
	A_CustomFinaleValue50 = 1

 	A_CustomFinale51 = ONSLAUGHT
	A_CustomFinaleValue51 = "c1m4_delay"   
                      
	//-----------------------------------------------------

	PreferredMobDirection = SPAWN_LARGE_VOLUME
	PreferredSpecialDirection = SPAWN_LARGE_VOLUME
        
//	BoomerLimit = 0
//	SmokerLimit = 2
//	HunterLimit = 1
//	SpitterLimit = 1
//	JockeyLimit = 0
//	ChargerLimit = 1

	ProhibitBosses = true
	ZombieSpawnRange = 3000
	MobRechargeRate = 0.5
	HordeEscapeCommonLimit = 15
	BileMobSize = 15
	SpecialRespawnInterval = 20
	
	MusicDynamicMobSpawnSize = 8
	MusicDynamicMobStopSize = 2
	MusicDynamicMobScanStopSize = 1
} 

InitialOnslaughtOptions <-
{
    LockTempo = 0
	IntensityRelaxThreshold = 1.1
	RelaxMinInterval = 2
	RelaxMaxInterval = 4
	SustainPeakMinTime = 25
	SustainPeakMaxTime = 30
	
	MobSpawnMinTime = 4
	MobSpawnMaxTime = 8
	MobMinSize = 2
	MobMaxSize = 6
	CommonLimit = 5
	
	SpecialRespawnInterval = 100
}

PanicOptions <-
{
	MegaMobSize = 0 // randomized in OnBeginCustomFinaleStage
	MegaMobMinSize = 20
	MegaMobMaxSize = 40
	MaxSpecials = 5
	BoomerLimit = 1
	SmokerLimit = 2
	HunterLimit = 2
	SpitterLimit = 1
	JockeyLimit = 1
	ChargerLimit = 1
	CommonLimit = 25
	
	SpecialRespawnInterval = 25
}

TankOptions <-
{
	ShouldAllowMobsWithTank = true
	ShouldAllowSpecialsWithTank = true

	MobSpawnMinTime = 20
	MobSpawnMaxTime = 40
	MobMinSize = 3
	MobMaxSize = 5
	MaxSpecials = 3
	CommonLimit = 5
	
	SpecialRespawnInterval = 50
}


DirectorOptions <- clone SharedOptions
{
}


//-----------------------------------------------------

// number of cans needed to escape.
NumCansNeeded <- 16

// fewer cans in single player since bots don't help much
if ( Director.IsSinglePlayerGame() )
{
	NumCansNeeded <- 10
}

// duration of delay stage.
DelayMin <- 10
DelayMax <- 20

// Number of touches and/or pours allowed before a delay is aborted.
DelayPourThreshold <- 1
DelayTouchedOrPouredThreshold <- 2


// Once the delay is aborted, amount of time before it progresses to next stage.
AbortDelayMin <- 1
AbortDelayMax <- 3

// Number of touches and pours it takes to transition out of c1m4_finale_wave_1
GimmeThreshold <- 4


// console overrides
if ( Director.IsPlayingOnConsole() )
{
	DelayMin <- 20
	DelayMax <- 30
	
	// Number of touches and/or pours allowed before a delay is aborted.
	DelayPourThreshold <- 2
	DelayTouchedOrPouredThreshold <- 4
	
	TankOptions.ShouldAllowSpecialsWithTank = false
}
//-----------------------------------------------------
//      INIT
//-----------------------------------------------------

GasCansTouched          <- 0
GasCansPoured           <- 0
DelayTouchedOrPoured    <- 0
DelayPoured             <- 0

EntFire( "timer_delay_end", "LowerRandomBound", DelayMin )
EntFire( "timer_delay_end", "UpperRandomBound", DelayMax )
EntFire( "timer_delay_abort", "LowerRandomBound", AbortDelayMin )
EntFire( "timer_delay_abort", "UpperRandomBound", AbortDelayMax )

// this is too late. Moved to c1m4_atrium.nut
//EntFire( "progress_display", "SetTotalItems", NumCansNeeded )

function AbortDelay(){}  	// only defined during a delay, in c1m4_delay.nut
function EndDelay(){}		// only defined during a delay, in c1m4_delay.nut

NavMesh.UnblockRescueVehicleNav()

//-----------------------------------------------------

function GasCanTouched()
{
    GasCansTouched++
    Msg(" Touched: " + GasCansTouched + "\n")   
     
    EvalGasCansPouredOrTouched()    
}
    
function GasCanPoured()
{
    GasCansPoured++
    DelayPoured++
    Msg(" Poured: " + GasCansPoured + "\n")   

    if ( GasCansPoured == NumCansNeeded )
    {
        Msg(" needed: " + NumCansNeeded + "\n") 
        EntFire( "relay_car_ready", "trigger" )
    }

    EvalGasCansPouredOrTouched()
}

function EvalGasCansPouredOrTouched()
{
    TouchedOrPoured <- GasCansPoured + GasCansTouched
    Msg(" Poured or touched: " + TouchedOrPoured + "\n")

    DelayTouchedOrPoured++
    Msg(" DelayTouchedOrPoured: " + DelayTouchedOrPoured + "\n")
    Msg(" DelayPoured: " + DelayPoured + "\n")
    
    if (( DelayTouchedOrPoured >= DelayTouchedOrPouredThreshold ) || ( DelayPoured >= DelayPourThreshold ))
    {
        AbortDelay()
    }
    
    switch( TouchedOrPoured )
    {
        case GimmeThreshold:
            EntFire( "@director", "EndCustomScriptedStage" )
            break
    }
}
//-----------------------------------------------------

function AddTableToTable( dest, src )
{
	foreach( key, val in src )
	{
		dest[key] <- val
	}
}

function OnBeginCustomFinaleStage( num, type )
{
	printl( "Beginning custom finale stage " + num + " of type " + type );
	
	local waveOptions = null
	if ( num == 1 )
	{
		waveOptions = InitialOnslaughtOptions
	}
	else if ( type == PANIC )
	{
		waveOptions = PanicOptions
		waveOptions.MegaMobSize = PanicOptions.MegaMobMinSize + rand()%( PanicOptions.MegaMobMaxSize - PanicOptions.MegaMobMinSize )
		
		Msg("*************************" + waveOptions.MegaMobSize + "\n")
		
	}
	else if ( type == TANK )
	{
		waveOptions = TankOptions
		EntFire( "bonus_relay", "Trigger", 0 )
	}
	
	// give out items at certain stages
	if ( num == 3 || num == 7 || num == 15 || num == 23 )
	{
		Director.L4D1SurvivorGiveItem()
	}
	
	//---------------------------------


	MapScript.DirectorOptions.clear()
	

	AddTableToTable( MapScript.DirectorOptions, SharedOptions );

	if ( waveOptions != null )
	{
		AddTableToTable( MapScript.DirectorOptions, waveOptions );
	}
	
	
	Director.ResetMobTimer()
	
	if ( developer() > 0 )
	{
		Msg( "\n*****\nMapScript.DirectorOptions:\n" );
		foreach( key, value in MapScript.DirectorOptions )
		{
			Msg( "    " + key + " = " + value + "\n" );
		}

		if ( LocalScript.rawin( "DirectorOptions" ) )
		{
			Msg( "\n*****\nLocalScript.DirectorOptions:\n" );
			foreach( key, value in LocalScript.DirectorOptions )
			{
				Msg( "    " + key + " = " + value + "\n" );
			}
		}
	}
}

//-----------------------------------------------------


if ( Director.GetGameModeBase() == "versus" )
{
	SharedOptions.ProhibitBosses = false
}

c7m3_port_finale.nuc

//-----------------------------------------------------
// This script handles the logic for the Port / Bridge
// finale in the River Campaign. 
//
//-----------------------------------------------------
Msg("Initiating c7m3_port_finale script\n");

//-----------------------------------------------------
ERROR		<- -1
PANIC 		<- 0
TANK 		<- 1
DELAY 		<- 2

//-----------------------------------------------------

// This keeps track of the number of times the generator button has been pressed. 
// Init to 1, since one button press has been used to start the finale and run 
// this script. 
ButtonPressCount <- 1

// This stores the stage number that we last
// played the "Press the Button!" VO
LastVOButtonStageNumber <- 0

// We use this to keep from running a bunch of queued advances really quickly. 
// Init to true because we are starting a finale from a button press in the pre-finale script 
// see GeneratorButtonPressed in c7m3_port.nut
PendingWaitAdvance <- true	

// We use three generator button presses to push through
// 8 stages. We have to queue up state advances
// depending on the state of the finale when buttons are pressed
QueuedDelayAdvances <- 0


// Tracking current finale states
CurrentFinaleStageNumber <- ERROR
CurrentFinaleStageType <- ERROR

// The finale is 3 phases. 
// We randomize the event types in the first two
local RandomFinaleStage1 = 0
local RandomFinaleStage2 = 0
local RandomFinaleStage4 = 0
local RandomFinaleStage5 = 0

// PHASE 1 EVENTS
if ( RandomInt( 1, 100 ) < 50 )
{
	RandomFinaleStage1 = PANIC
	RandomFinaleStage2 = TANK
}
else
{
	RandomFinaleStage1 = TANK
	RandomFinaleStage2 = PANIC
}


// PHASE 2 EVENTS
if ( RandomInt( 1, 100 ) < 50 )
{
	RandomFinaleStage4 = PANIC
	RandomFinaleStage5 = TANK
}
else
{
	RandomFinaleStage4 = TANK
	RandomFinaleStage5 = PANIC
}



// We want to give the survivors a little of extra time to 
// get on their feet before the escape, since you have to fight through 
// the sacrifice.

PreEscapeDelay <- 0
if ( Director.GetGameModeBase() == "coop" || Director.GetGameModeBase() == "realism" )
{
	PreEscapeDelay <- 5
}
else if ( Director.GetGameModeBase() == "versus" )
{
	PreEscapeDelay <- 15
}

DirectorOptions <-
{	
	A_CustomFinale_StageCount = 8
	
	// PHASE 1
	A_CustomFinale1 = RandomFinaleStage1
	A_CustomFinaleValue1 = 1
	A_CustomFinale2 = RandomFinaleStage2
	A_CustomFinaleValue2 = 1
	A_CustomFinale3 = DELAY
	A_CustomFinaleValue3 = 9999
	
	
	// PHASE 2
	A_CustomFinale4 = RandomFinaleStage4
	A_CustomFinaleValue4 = 1
	A_CustomFinale5 = RandomFinaleStage5
	A_CustomFinaleValue5 = 1	
	A_CustomFinale6 = DELAY
	A_CustomFinaleValue6 = 9999 	 
	
	
	// PHASE 3
	A_CustomFinale7 = TANK
	A_CustomFinaleValue7 = 1	 	 		 
	A_CustomFinale8 = DELAY
	A_CustomFinaleValue8 = PreEscapeDelay
	
	
	
	TankLimit = 4
	WitchLimit = 0
	CommonLimit = 20	
	HordeEscapeCommonLimit = 15	
	EscapeSpawnTanks = false
	//SpecialRespawnInterval = 80
}


function OnBeginCustomFinaleStage( num, type )
{
	printl( "*!* Beginning custom finale stage " + num + " of type " + type );
	printl( "*!* PendingWaitAdvance " + PendingWaitAdvance + ", QueuedDelayAdvances " + QueuedDelayAdvances );
	
	// Store off the state... 
	CurrentFinaleStageNumber = num
	CurrentFinaleStageType = type
	
	// Acknowledge the state advance
	PendingWaitAdvance = false
}


function GeneratorButtonPressed()
{
    printl( "*!* GeneratorButtonPressed finale stage " + CurrentFinaleStageNumber + " of type " +CurrentFinaleStageType );
	printl( "*!* PendingWaitAdvance " + PendingWaitAdvance + ", QueuedDelayAdvances " + QueuedDelayAdvances );
	
	
	ButtonPressCount++
	
	
	local ImmediateAdvances = 0
	
	
	if ( CurrentFinaleStageNumber == 1 || CurrentFinaleStageNumber == 4 )
	{		
		// First stage of a phase, so next stage is an "action" stage too.
		// Advance to next action stage, and then queue an advance to the 
		// next delay.
		QueuedDelayAdvances++
		ImmediateAdvances = 1
	}
	else if ( CurrentFinaleStageNumber == 2 || CurrentFinaleStageNumber == 5 )
	{
		// Second stage of a phase, so next stage is a "delay" stage.
		// We need to immediately advance past the delay and into an action state. 
		
		//QueuedDelayAdvances++	// NOPE!
		ImmediateAdvances = 2
	}
	else if ( CurrentFinaleStageNumber == 3 || CurrentFinaleStageNumber == 6 )
	{
		// Wait states... (very long delay)
		// Advance immediately into an action state
		
		//QueuedDelayAdvances++
		ImmediateAdvances = 1
	}
	else if ( CurrentFinaleStageNumber == -1 || 
              CurrentFinaleStageNumber == 0 )
	{
		// the finale is *just* about to start... 
		// we can get this if all the buttons are hit at once at the beginning
		// Just queue a wait advance
		QueuedDelayAdvances++
		ImmediateAdvances = 0
	}
	else
	{
		printl( "*!* Unhandled generator button press! " );
	}

	if ( ImmediateAdvances > 0 )
	{	
		EntFire( "generator_start_model", "Enable" )
		
		
		if ( ImmediateAdvances == 1 )
		{
			printl( "*!* GeneratorButtonPressed Advancing State ONCE");
			EntFire( "generator_start_model", "AdvanceFinaleState" )
		}
		else if ( ImmediateAdvances == 2 )
		{
			printl( "*!* GeneratorButtonPressed Advancing State TWICE");
			EntFire( "generator_start_model", "AdvanceFinaleState" )
			EntFire( "generator_start_model", "AdvanceFinaleState" )
		}
		
		EntFire( "generator_start_model", "Disable" )
		
		PendingWaitAdvance = true
	}
	
}

function Update()
{
	// Should we advance the finale state?
	// 1. If we're in a DELAY state
	// 2. And we have queued advances.... 
	// 3. And we haven't just tried to advance the advance the state.... 
	if ( CurrentFinaleStageType == DELAY && QueuedDelayAdvances > 0 && !PendingWaitAdvance )
	{
		// If things are calm (relatively), jump to the next state
		if ( !Director.IsTankInPlay() && !Director.IsAnySurvivorInCombat() )
		{
			if ( Director.GetPendingMobCount() < 1 && Director.GetCommonInfectedCount() < 5 )
			{
				printl( "*!* Update Advancing State finale stage " + CurrentFinaleStageNumber + " of type " +CurrentFinaleStageType );
				printl( "*!* PendingWaitAdvance " + PendingWaitAdvance + ", QueuedDelayAdvances " + QueuedDelayAdvances );
		
				QueuedDelayAdvances--
				EntFire( "generator_start_model", "Enable" )
				EntFire( "generator_start_model", "AdvanceFinaleState" )
				EntFire( "generator_start_model", "Disable" )
				PendingWaitAdvance = true
			}
		}
	}
	
	// Should we fire the director event to play the "Press the button!" Nag VO?	
	// If we're on an infinite delay stage...
	if ( CurrentFinaleStageType == DELAY && CurrentFinaleStageNumber > 1 && CurrentFinaleStageNumber < 7 )	
	{		
		// 1. We haven't nagged for this stage yet
		// 2. There are button presses remaining
		if ( CurrentFinaleStageNumber != LastVOButtonStageNumber && ButtonPressCount < 3 )
		{
			// We're not about to process a wait advance..
			if ( QueuedDelayAdvances == 0 && !PendingWaitAdvance )
			{
				// If things are pretty calm, run the event
				if ( Director.GetPendingMobCount() < 1 && Director.GetCommonInfectedCount() < 1 )
				{
					if ( !Director.IsTankInPlay() && !Director.IsAnySurvivorInCombat() )
					{
						printl( "*!* Update firing event 1 (VO Prompt)" )
						LastVOButtonStageNumber = CurrentFinaleStageNumber
						Director.UserDefinedEvent1()
					}
				}
			}
		}
	}
	
}


function EnableEscapeTanks()
{
	printl( "*!* EnableEscapeTanks finale stage " + CurrentFinaleStageNumber + " of type " +CurrentFinaleStageType );
	
	//Msg( "\n*****\nMapScript.DirectorOptions:\n" );
	//foreach( key, value in MapScript.DirectorOptions )
	//{
	//	Msg( "    " + key + " = " + value + "\n" );
	//}

	MapScript.DirectorOptions.EscapeSpawnTanks <- true
}

c8m5_rooftop_finale.nuc

//-----------------------------------------------------
//
//
//-----------------------------------------------------
Msg("Initiating c8m5_rooftop_finale script\n");

//-----------------------------------------------------
ERROR		<- -1
PANIC 		<- 0
TANK 		<- 1
DELAY 		<- 2
SCRIPTED 	<- 3
//-----------------------------------------------------

StageDelay <- 0
PreEscapeDelay <- 0
if ( Director.GetGameModeBase() == "coop" || Director.GetGameModeBase() == "realism" )
{
	StageDelay <- 5
	PreEscapeDelay <- 5
}
else if ( Director.GetGameModeBase() == "versus" )
{
	StageDelay <- 10
	PreEscapeDelay <- 15
}

DirectorOptions <-
{	
	A_CustomFinale_StageCount = 8
	
	A_CustomFinale1 		= PANIC
	A_CustomFinaleValue1 	= 2
	A_CustomFinale2 		= DELAY
	A_CustomFinaleValue2 	= StageDelay
	A_CustomFinale3 		= TANK
	A_CustomFinaleValue3 	= 1
	A_CustomFinale4 		= DELAY
	A_CustomFinaleValue4 	= StageDelay
	A_CustomFinale5 		= PANIC
	A_CustomFinaleValue5 	= 2
	A_CustomFinaleMusic5 	= "Event.FinaleWave4"
	A_CustomFinale6 		= DELAY
	A_CustomFinaleValue6 	= StageDelay
	A_CustomFinale7 		= TANK
	A_CustomFinaleValue7 	= 1
	A_CustomFinale8 		= DELAY
	A_CustomFinaleValue8 	= PreEscapeDelay
	
	TankLimit = 1
	WitchLimit = 0
	CommonLimit = 20
	HordeEscapeCommonLimit = 15
	EscapeSpawnTanks = false
	//SpecialRespawnInterval = 80
	
	MusicDynamicMobSpawnSize = 8
	MusicDynamicMobStopSize = 2
	MusicDynamicMobScanStopSize = 1
}

function EnableEscapeTanks()
{
	printl( "Chase Tanks Enabled!" );
	
	MapScript.DirectorOptions.EscapeSpawnTanks <- true
}

function OnBeginCustomFinaleStage( num, type )
{
	//printl( "Beginning custom finale stage " + num + " of type " + type );
	
	if ( type == 2 )
		EntFire( "pilot", "SpeakResponseConcept", "hospital_radio_intransit" );
}

c9m2_lots_finale.nuc

//-----------------------------------------------------
//
//
//-----------------------------------------------------
Msg("Initiating c9m2_lots_finale script\n");

//-----------------------------------------------------
ERROR		<- -1
PANIC 		<- 0
TANK 		<- 1
DELAY 		<- 2
SCRIPTED 	<- 3
//-----------------------------------------------------

StageDelay <- 0
PreEscapeDelay <- 0
if ( Director.GetGameModeBase() == "coop" || Director.GetGameModeBase() == "realism" )
{
	StageDelay <- 5
	PreEscapeDelay <- 5
}
else if ( Director.GetGameModeBase() == "versus" )
{
	StageDelay <- 10
	PreEscapeDelay <- 15
}

DirectorOptions <-
{	
	A_CustomFinale_StageCount = 8
	
	A_CustomFinale1 		= PANIC
	A_CustomFinaleValue1 	= 2
	A_CustomFinale2 		= DELAY
	A_CustomFinaleValue2 	= StageDelay
	A_CustomFinale3 		= TANK
	A_CustomFinaleValue3 	= 1
	A_CustomFinale4 		= DELAY
	A_CustomFinaleValue4 	= StageDelay
	A_CustomFinale5 		= PANIC
	A_CustomFinaleValue5 	= 2
	A_CustomFinaleMusic5 	= "Event.FinaleWave4"
	A_CustomFinale6 		= DELAY
	A_CustomFinaleValue6 	= StageDelay
	A_CustomFinale7 		= TANK
	A_CustomFinaleValue7 	= 1
	A_CustomFinale8 		= DELAY
	A_CustomFinaleValue8 	= PreEscapeDelay
	
	TankLimit = 1
	WitchLimit = 0
	CommonLimit = 20
	HordeEscapeCommonLimit = 15
	EscapeSpawnTanks = false
	//SpecialRespawnInterval = 80
	
	MusicDynamicMobSpawnSize = 8
	MusicDynamicMobStopSize = 2
	MusicDynamicMobScanStopSize = 1
}

function EnableEscapeTanks()
{
	printl( "Chase Tanks Enabled!" );
	
	MapScript.DirectorOptions.EscapeSpawnTanks <- true
}

c10m5_houseboat_finale.nuc

//-----------------------------------------------------
//
//
//-----------------------------------------------------
Msg("Initiating c10m5_houseboat_finale script\n");

//-----------------------------------------------------
ERROR		<- -1
PANIC 		<- 0
TANK 		<- 1
DELAY 		<- 2
SCRIPTED 	<- 3
//-----------------------------------------------------

StageDelay <- 0
PreEscapeDelay <- 0
if ( Director.GetGameModeBase() == "coop" || Director.GetGameModeBase() == "realism" )
{
	StageDelay <- 5
	PreEscapeDelay <- 5
}
else if ( Director.GetGameModeBase() == "versus" )
{
	StageDelay <- 10
	PreEscapeDelay <- 15
}

DirectorOptions <-
{	
	A_CustomFinale_StageCount = 8
	
	A_CustomFinale1 		= PANIC
	A_CustomFinaleValue1 	= 2
	A_CustomFinale2 		= DELAY
	A_CustomFinaleValue2 	= StageDelay
	A_CustomFinale3 		= TANK
	A_CustomFinaleValue3 	= 1
	A_CustomFinale4 		= DELAY
	A_CustomFinaleValue4 	= StageDelay
	A_CustomFinale5 		= PANIC
	A_CustomFinaleValue5 	= 2
	A_CustomFinaleMusic5 	= "Event.FinaleWave4"
	A_CustomFinale6 		= DELAY
	A_CustomFinaleValue6 	= StageDelay
	A_CustomFinale7 		= TANK
	A_CustomFinaleValue7 	= 1
	A_CustomFinale8 		= DELAY
	A_CustomFinaleValue8 	= PreEscapeDelay
	
	TankLimit = 1
	WitchLimit = 0
	CommonLimit = 20
	HordeEscapeCommonLimit = 15
	EscapeSpawnTanks = false
	//SpecialRespawnInterval = 80
	
	MusicDynamicMobSpawnSize = 8
	MusicDynamicMobStopSize = 2
	MusicDynamicMobScanStopSize = 1
}

function EnableEscapeTanks()
{
	printl( "Chase Tanks Enabled!" );
	
	MapScript.DirectorOptions.EscapeSpawnTanks <- true
}

function OnBeginCustomFinaleStage( num, type )
{
	//printl( "Beginning custom finale stage " + num + " of type " + type );
	
	if ( type == 2 )
		EntFire( "orator_boat_radio", "SpeakResponseConcept", "boat_radio_intransit" );
}

c11m5_runway_finale.nuc

//-----------------------------------------------------
//
//
//-----------------------------------------------------
Msg("Initiating c11m5_runway_finale script\n");

//-----------------------------------------------------
ERROR		<- -1
PANIC 		<- 0
TANK 		<- 1
DELAY 		<- 2
SCRIPTED 	<- 3
//-----------------------------------------------------

StageDelay <- 0
PreEscapeDelay <- 0
if ( Director.GetGameModeBase() == "coop" || Director.GetGameModeBase() == "realism" )
{
	StageDelay <- 5
	PreEscapeDelay <- 5
}
else if ( Director.GetGameModeBase() == "versus" )
{
	StageDelay <- 10
	PreEscapeDelay <- 15
}

DirectorOptions <-
{	
	A_CustomFinale_StageCount = 8
	
	A_CustomFinale1 		= PANIC
	A_CustomFinaleValue1 	= 2
	A_CustomFinale2 		= DELAY
	A_CustomFinaleValue2 	= StageDelay
	A_CustomFinale3 		= TANK
	A_CustomFinaleValue3 	= 1
	A_CustomFinale4 		= DELAY
	A_CustomFinaleValue4 	= StageDelay
	A_CustomFinale5 		= PANIC
	A_CustomFinaleValue5 	= 2
	A_CustomFinaleMusic5 	= "Event.FinaleWave4"
	A_CustomFinale6 		= DELAY
	A_CustomFinaleValue6 	= StageDelay
	A_CustomFinale7 		= TANK
	A_CustomFinaleValue7 	= 1
	A_CustomFinale8 		= DELAY
	A_CustomFinaleValue8 	= PreEscapeDelay
	
	TankLimit = 1
	WitchLimit = 0
	CommonLimit = 20
	HordeEscapeCommonLimit = 15
	EscapeSpawnTanks = false
	//SpecialRespawnInterval = 80
	
	MusicDynamicMobSpawnSize = 8
	MusicDynamicMobStopSize = 2
	MusicDynamicMobScanStopSize = 1
}

function EnableEscapeTanks()
{
	printl( "Chase Tanks Enabled!" );
	
	MapScript.DirectorOptions.EscapeSpawnTanks <- true
}

function OnBeginCustomFinaleStage( num, type )
{
	//printl( "Beginning custom finale stage " + num + " of type " + type );
	
	if ( type == 2 )
		EntFire( "orator_plane_radio", "SpeakResponseConcept", "plane_radio_intransit" );
}

c12m5_cornfield_finale.nuc

//-----------------------------------------------------
//
//
//-----------------------------------------------------
Msg("Initiating c12m5_cornfield_finale script\n");

//-----------------------------------------------------
ERROR		<- -1
PANIC 		<- 0
TANK 		<- 1
DELAY 		<- 2
SCRIPTED 	<- 3
//-----------------------------------------------------

StageDelay <- 0
PreEscapeDelay <- 0
if ( Director.GetGameModeBase() == "coop" || Director.GetGameModeBase() == "realism" )
{
	StageDelay <- 5
	PreEscapeDelay <- 5
}
else if ( Director.GetGameModeBase() == "versus" )
{
	StageDelay <- 10
	PreEscapeDelay <- 15
}

DirectorOptions <-
{	
	A_CustomFinale_StageCount = 8
	
	A_CustomFinale1 		= PANIC
	A_CustomFinaleValue1 	= 2
	A_CustomFinale2 		= DELAY
	A_CustomFinaleValue2 	= StageDelay
	A_CustomFinale3 		= TANK
	A_CustomFinaleValue3 	= 1
	A_CustomFinale4 		= DELAY
	A_CustomFinaleValue4 	= StageDelay
	A_CustomFinale5 		= PANIC
	A_CustomFinaleValue5 	= 2
	A_CustomFinaleMusic5 	= "Event.FinaleWave4"
	A_CustomFinale6 		= DELAY
	A_CustomFinaleValue6 	= StageDelay
	A_CustomFinale7 		= TANK
	A_CustomFinaleValue7 	= 1
	A_CustomFinale8 		= DELAY
	A_CustomFinaleValue8 	= PreEscapeDelay
	
	TankLimit = 1
	WitchLimit = 0
	CommonLimit = 20
	HordeEscapeCommonLimit = 15
	EscapeSpawnTanks = false
	//SpecialRespawnInterval = 80
	
	MusicDynamicMobSpawnSize = 8
	MusicDynamicMobStopSize = 2
	MusicDynamicMobScanStopSize = 1
}

function EnableEscapeTanks()
{
	printl( "Chase Tanks Enabled!" );
	
	MapScript.DirectorOptions.EscapeSpawnTanks <- true
}

function OnBeginCustomFinaleStage( num, type )
{
	//printl( "Beginning custom finale stage " + num + " of type " + type );
	
	if ( type == 2 )
		EntFire( "orator_farm_radio", "SpeakResponseConcept", "farm_radio_intransit" );
}

c14m1_lighthouse_finale.nuc

Msg("----------------------FINALE SCRIPT------------------\n")

StageDelay <- 0
PreEscapeDelay <- 0
if ( Director.GetGameModeBase() == "coop" || Director.GetGameModeBase() == "realism" )
{
	StageDelay <- 5
	PreEscapeDelay <- 5
}
else if ( Director.GetGameModeBase() == "versus" )
{
	StageDelay <- 10
	PreEscapeDelay <- 15
}

//-----------------------------------------------------
PANIC <- 0
TANK <- 1
DELAY <- 2
ONSLAUGHT <- 3
//-----------------------------------------------------

DirectorOptions <-
{
 	A_CustomFinale_StageCount = 14
	
	A_CustomFinale1			= ONSLAUGHT
	A_CustomFinaleValue1	= "c14m1_gauntlet"
	A_CustomFinale2 		= DELAY
	A_CustomFinaleValue2 	= StageDelay
	A_CustomFinale3 		= TANK
	A_CustomFinaleValue3 	= 1	
	A_CustomFinale4 		= DELAY
	A_CustomFinaleValue4 	= StageDelay
	A_CustomFinale5 		= PANIC
	A_CustomFinaleValue5 	= 2
	A_CustomFinale6 		= DELAY
	A_CustomFinaleValue6 	= StageDelay
	A_CustomFinale7			= ONSLAUGHT
	A_CustomFinaleValue7 	= "c14m1_gauntlet"
	A_CustomFinale8 		= DELAY
	A_CustomFinaleValue8 	= StageDelay
	A_CustomFinale9			= TANK
	A_CustomFinaleValue9	= 1
	A_CustomFinale10 		= DELAY
	A_CustomFinaleValue10 	= StageDelay
	A_CustomFinale11 		= PANIC
	A_CustomFinaleValue11 	= 2	
	A_CustomFinale12 		= DELAY
	A_CustomFinaleValue12 	= StageDelay
	A_CustomFinale13 		= TANK
	A_CustomFinaleValue13 	= 2
	A_CustomFinaleMusic13	= "Event.TankMidpoint_Metal"
	A_CustomFinale14 		= DELAY
	A_CustomFinaleValue14 	= PreEscapeDelay
	//-----------------------------------------------------

	ProhibitBosses = true
} 

//-----------------------------------------------------

// number of cans needed to escape.
NumCansNeeded <- 8

// fewer cans in single player since bots don't help much
/*if ( Director.IsSinglePlayerGame() )
{
	NumCansNeeded <- 6
}*/
//-----------------------------------------------------
//      INIT
//-----------------------------------------------------

GasCansTouched          <- 0
GasCansPoured           <- 0

//NavMesh.UnblockRescueVehicleNav()

//-----------------------------------------------------

function GasCanTouched()
{
    GasCansTouched++
    Msg(" Touched: " + GasCansTouched + "\n")   
	
    EvalGasCansPouredOrTouched()    
}
    
function GasCanPoured()
{
    GasCansPoured++
    Msg(" Poured: " + GasCansPoured + "\n")

    if ( GasCansPoured == NumCansNeeded )
    {
        Msg(" needed: " + NumCansNeeded + "\n") 
        EntFire( "relay_generator_ready", "Trigger" )
    }

    EvalGasCansPouredOrTouched()
}

function EvalGasCansPouredOrTouched()
{
    TouchedOrPoured <- GasCansPoured + GasCansTouched
    Msg(" Poured or touched: " + TouchedOrPoured + "\n")
}
//-----------------------------------------------------

function OnBeginCustomFinaleStage( num, type )
{
	printl( "Beginning custom finale stage " + num + " of type " + type );
	
	if ( num == 7 )
	{
		EntFire( "relay_lighthouse_off", "Trigger" );
	}
}

c14m2_lighthouse_finale.nuc

Msg("Initiating c14m2_lighthouse_finale script\n");

StageDelay <- 15
PreEscapeDelay <- 10

//-----------------------------------------------------
PANIC <- 0
TANK <- 1
DELAY <- 2
ONSLAUGHT <- 3
//-----------------------------------------------------

DirectorOptions <-
{
	A_CustomFinale_StageCount = 8
	
	A_CustomFinale1 		= PANIC
	A_CustomFinaleValue1 	= 2
	A_CustomFinale2 		= DELAY
	A_CustomFinaleValue2 	= StageDelay
	A_CustomFinale3 		= TANK
	A_CustomFinaleValue3 	= 1
	A_CustomFinale4 		= DELAY
	A_CustomFinaleValue4 	= StageDelay
	A_CustomFinale5			= ONSLAUGHT
	A_CustomFinaleValue5 	= "c14m2_gauntlet"
	A_CustomFinale6 		= DELAY
	A_CustomFinaleValue6 	= StageDelay
	A_CustomFinale7			= TANK
	A_CustomFinaleValue7	= 2
	A_CustomFinaleMusic7	= "Event.TankMidpoint_Metal"
	A_CustomFinale8 		= DELAY
	A_CustomFinaleValue8 	= PreEscapeDelay
	//-----------------------------------------------------

	ProhibitBosses = true
	HordeEscapeCommonLimit = 20
	EscapeSpawnTanks = false
}

local difficulty = Convars.GetStr( "z_difficulty" ).tolower();

if ( Director.GetGameModeBase() == "versus" )
{
	DirectorOptions.rawdelete("A_CustomFinaleMusic7");
	DirectorOptions.A_CustomFinale_StageCount = 11;
	DirectorOptions.A_CustomFinale6 = ONSLAUGHT;
	DirectorOptions.A_CustomFinaleValue6 = "c14m2_gauntlet_vs";
	DirectorOptions.A_CustomFinale7 = ONSLAUGHT;
	DirectorOptions.A_CustomFinaleValue7 = "c14m2_gauntlet_vs";
	DirectorOptions.A_CustomFinale8 = ONSLAUGHT;
	DirectorOptions.A_CustomFinaleValue8 = "c14m2_gauntlet_vs";
	DirectorOptions.A_CustomFinale9 <- DELAY;
	DirectorOptions.A_CustomFinaleValue9 <- StageDelay;
	DirectorOptions.A_CustomFinale10 <- TANK;
	DirectorOptions.A_CustomFinaleValue10 <- 1;
	DirectorOptions.A_CustomFinaleMusic10 <- "Event.TankMidpoint_Metal";
	DirectorOptions.A_CustomFinale11 <- DELAY;
	DirectorOptions.A_CustomFinaleValue11 <- PreEscapeDelay;
	difficulty = "normal";
}
else
{
	if ( difficulty == "hard" || difficulty == "impossible" )
	{
		DirectorOptions.rawdelete("A_CustomFinaleMusic7");
		DirectorOptions.A_CustomFinale_StageCount = 12;
		DirectorOptions.A_CustomFinaleValue7 = 1;
		DirectorOptions.A_CustomFinaleValue8 = StageDelay;
		DirectorOptions.A_CustomFinale9 <- PANIC;
		DirectorOptions.A_CustomFinaleValue9 <- 2;
		DirectorOptions.A_CustomFinale10 <- DELAY;
		DirectorOptions.A_CustomFinaleValue10 <- StageDelay;
		DirectorOptions.A_CustomFinale11 <- TANK;
		DirectorOptions.A_CustomFinaleValue11 <- 2;
		DirectorOptions.A_CustomFinaleMusic11 <- "Event.TankMidpoint_Metal"
		DirectorOptions.A_CustomFinale12 <- DELAY;
		DirectorOptions.A_CustomFinaleValue12 <- PreEscapeDelay;
	}
}

//-----------------------------------------------------

function SpawnScavengeCans( difficulty )
{
	local function SpawnCan( gascan )
	{
		local can_origin = gascan.GetOrigin();
		local can_angles = gascan.GetAngles();
		gascan.Kill();
		
		local kvs =
		{
			angles = can_angles.ToKVString()
			body = 0
			disableshadows = 1
			glowstate = 3
			model = "models/props_junk/gascan001a.mdl"
			skin = 2
			weaponskin = 2
			solid = 0
			spawnflags = 2
			targetname = "scavenge_gascans"
			origin = can_origin.ToKVString()
			connections =
			{
				OnItemPickedUp =
				{
					cmd1 = "director�RunScriptCode�DirectorScript.MapScript.LocalScript.GasCanTouched()�0�-1"
					//Editors Note: These � characters will not copy over properly. Websites cannot display them.
					//In Notepad++ replace them by selecting them, holding down ALT and entering 027 on Numblock. You'll get a black ESC character.
				}
			}
		}
		local can_spawner = SpawnEntityFromTable( "weapon_scavenge_item_spawn", kvs );
		if ( can_spawner )
			DoEntFire( "!self", "SpawnItem", "", 0, null, can_spawner );
	}
	
	switch( difficulty )
	{
		case "impossible":
		{
			local gascan = null;
			while ( gascan = Entities.FindByName( gascan, "gascans_finale_expert" ) )
			{
				if ( gascan.IsValid() )
					SpawnCan( gascan );
			}
		}
		case "hard":
		{
			local gascan = null;
			while ( gascan = Entities.FindByName( gascan, "gascans_finale_advanced" ) )
			{
				if ( gascan.IsValid() )
					SpawnCan( gascan );
			}
		}
		case "normal":
		{
			local gascan = null;
			while ( gascan = Entities.FindByName( gascan, "gascans_finale_normal" ) )
			{
				if ( gascan.IsValid() )
					SpawnCan( gascan );
			}
		}
		case "easy":
		{
			local gascan = null;
			while ( gascan = Entities.FindByName( gascan, "gascans_finale_easy" ) )
			{
				if ( gascan.IsValid() )
					SpawnCan( gascan );
			}
			break;
		}
		default:
			break;
	}
	
	EntFire( "gascans_finale_*", "Kill" );
}

// number of cans needed to escape.
NumCansNeeded <- 8

switch( difficulty )
{
	case "easy":
	{
		NumCansNeeded = 6;
		EntFire( "relay_outro_easy", "Enable" );
		break;
	}
	case "normal":
	{
		NumCansNeeded = 8;
		EntFire( "relay_outro_normal", "Enable" );
		break;
	}
	case "hard":
	{
		NumCansNeeded = 10;
		EntFire( "relay_outro_advanced", "Enable" );
		break;
	}
	case "impossible":
	{
		NumCansNeeded = 12;
		EntFire( "relay_outro_expert", "Enable" );
		break;
	}
	default:
		break;
}

EntFire( "progress_display", "SetTotalItems", NumCansNeeded );
EntFire( "radio", "AddOutput", "FinaleEscapeStarted director:RunScriptCode:DirectorScript.MapScript.LocalScript.DirectorOptions.TankLimit <- 3:0:-1" );

local c14m2_tankspawntime = 0.0;
local c14m2_tankspawner = null;
while ( c14m2_tankspawner = Entities.FindByClassname( c14m2_tankspawner, "commentary_zombie_spawner" ) )
{
	if ( c14m2_tankspawner.IsValid() )
	{
		c14m2_tankspawner.ValidateScriptScope();
		local spawnerScope = c14m2_tankspawner.GetScriptScope();
		spawnerScope.SpawnedTankTime <- 0.0;
		spawnerScope.InputSpawnZombie <- function()
		{
			if ( (caller) && (caller.GetName() == "escapetanktrigger") )
			{
				if ( !c14m2_tankspawntime )
					c14m2_tankspawntime = Time();
			}
			if ( SpawnedTankTime )
			{
				if ( Time() - c14m2_tankspawntime < 1 )
					return false;
				else
				{
					delete this.SpawnedTankTime;
					delete this.InputSpawnZombie;
					return true;
				}
			}
			
			SpawnedTankTime = Time();
			return true;
		}
	}
}

//-----------------------------------------------------
//      INIT
//-----------------------------------------------------

GasCansTouched          <- 0
GasCansPoured           <- 0
ScavengeCansPoured		<- 0
ScavengeCansNeeded		<- 2

local EscapeStage = DirectorOptions.A_CustomFinale_StageCount;

//-----------------------------------------------------

function GasCanTouched()
{
	GasCansTouched++;
	if ( developer() > 0 )
		Msg(" Touched: " + GasCansTouched + "\n");
}

function GasCanPoured()
{
	GasCansPoured++;
	ScavengeCansPoured++;
	if ( developer() > 0 )
		Msg(" Poured: " + GasCansPoured + "\n");

	if ( GasCansPoured == 1 )
		EntFire( "explain_fuel_generator", "Kill" );
	else if ( GasCansPoured == NumCansNeeded )
	{
		if ( developer() > 0 )
			Msg(" needed: " + NumCansNeeded + "\n");
		EntFire( "relay_generator_ready", "Trigger", "", 0.1 );
		EntFire( "weapon_scavenge_item_spawn", "TurnGlowsOff" );
		EntFire( "weapon_scavenge_item_spawn", "Kill" );
		EntFire( "director", "EndCustomScriptedStage", "", 5 );
	}
	
	if ( Director.GetGameModeBase() == "versus" && ScavengeCansPoured == 2 && GasCansPoured < NumCansNeeded )
	{
		ScavengeCansPoured = 0;
		EntFire( "radio", "AdvanceFinaleState" );
	}
}
//-----------------------------------------------------

function OnBeginCustomFinaleStage( num, type )
{
	if ( developer() > 0 )
		printl( "Beginning custom finale stage " + num + " of type " + type );
	
	if ( num == 4 )
	{
		EntFire( "relay_boat_coming2", "Trigger" );
		// Delay lasts 10 seconds, next stage turns off lights immediately
		EntFire( "lighthouse_light", "SetPattern", "mmamammmmammamamaaamammma", 7.0 );
		EntFire( "lighthouse_light", "SetPattern", "", 9.5 );
		EntFire( "lighthouse_light", "TurnOff", "", 10 );
		EntFire( "spotlight_beams", "LightOff", "", 7.0 );
		EntFire( "spotlight_glow", "HideSprite", "", 7.0 );
		EntFire( "brush_light", "Enable", "", 7.0 );
		EntFire( "spotlight_beams", "LightOn", "", 7.5 );
		EntFire( "spotlight_glow", "ShowSprite", "", 7.5 );
		EntFire( "brush_light", "Disable", "", 7.5 );
		EntFire( "spotlight_beams", "LightOff", "", 8.0 );
		EntFire( "spotlight_glow", "HideSprite", "", 8.0 );
		EntFire( "brush_light", "Enable", "", 8.0 );
		EntFire( "spotlight_beams", "LightOn", "", 8.5 );
		EntFire( "spotlight_glow", "ShowSprite", "", 8.5 );
		EntFire( "brush_light", "Disable", "", 8.5 );
	}
	else if ( num == 5 )
	{
		EntFire( "relay_lighthouse_off", "Trigger" );
		SpawnScavengeCans( difficulty );
	}
	else if ( num == EscapeStage )
		EntFire( "relay_start_boat", "Trigger" );
}

function GetCustomScriptedStageProgress( defvalue )
{
	local progress = ScavengeCansPoured.tofloat() / ScavengeCansNeeded.tofloat();
	if ( developer() > 0 )
		Msg( "Progress was " + defvalue + ", now: " + ScavengeCansPoured + " poured / " + ScavengeCansNeeded + " needed = " + progress + "\n" );
	return progress;
}

director_gauntlet.nuc (为所有长途救援结局共享)

Msg("Initiating Gauntlet\n");

DirectorOptions <-
{
	PanicForever = true
	PausePanicWhenRelaxing = true

	IntensityRelaxThreshold = 0.99
	RelaxMinInterval = 25
	RelaxMaxInterval = 35
	RelaxMaxFlowTravel = 400

	LockTempo = 0
	SpecialRespawnInterval = 20
	PreTankMobMax = 20
	ZombieSpawnRange = 3000
	ZombieSpawnInFog = true

	MobSpawnSize = 5
	CommonLimit = 5

	GauntletMovementThreshold = 500.0
	GauntletMovementTimerLength = 5.0
	GauntletMovementBonus = 2.0
	GauntletMovementBonusMax = 30.0

	// length of bridge to test progress against.
	BridgeSpan = 20000

	MobSpawnMinTime = 5
	MobSpawnMaxTime = 5

	MobSpawnSizeMin = 5
	MobSpawnSizeMax = 20

	minSpeed = 50
	maxSpeed = 200

	speedPenaltyZAdds = 15

	CommonLimitMax = 30

	function RecalculateLimits()
	{
	//Increase common limit based on progress  
	    local progressPct = ( Director.GetFurthestSurvivorFlow() / BridgeSpan )
	    
	    if ( progressPct < 0.0 ) progressPct = 0.0;
	    if ( progressPct > 1.0 ) progressPct = 1.0;
	    
	    MobSpawnSize = MobSpawnSizeMin + progressPct * ( MobSpawnSizeMax - MobSpawnSizeMin )


	//Increase common limit based on speed   
	    local speedPct = ( Director.GetAveragedSurvivorSpeed() - minSpeed ) / ( maxSpeed - minSpeed );

	    if ( speedPct < 0.0 ) speedPct = 0.0;
	    if ( speedPct > 1.0 ) speedPct = 1.0;

	    MobSpawnSize = MobSpawnSize + speedPct * ( speedPenaltyZAdds );
	    
	    CommonLimit = MobSpawnSize * 1.5
	    
	    if ( CommonLimit > CommonLimitMax ) CommonLimit = CommonLimitMax;
	    

	}
}

function Update()
{
	DirectorOptions.RecalculateLimits();
}

另请参阅

介绍

文档

其他


求生之路2关卡设计/长途救援结局 Return to 求生之路2关卡设计