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关卡设计