--[[ * IMPORTS * ]]

Import "CombatScripts.lua"
Import "AudioScripts.lua"
Import "PostMatchScripts.lua"
Import "PlayerSkillScripts"
Import "MatchTutorial.lua"
Import "LeagueUtilityScripts"
Import "ItemPurchaseScripts"
Import "MatchRecord"
Import "OvermapData"
Import "DraftScripts"
Import "MPScripts"
Import "MatchEvents"
Import "MatchAI"
Import "MatchCamera"
Import "StarPickScripts"

--[[ * PACKAGES * ]]

Using "SelectionStar"

Using "Sunbeam"
Using "SunbeamHaze"
Using "SunbeamLightRoster"
Using "DraftPickedGlow"
Using "DraftChoose"
Using "DraftSwitchFx"
Using "ThinSmoke"

-- Celebration Sequence
Using "Button_Circle2"
Using "Button_Cross2"
Using "Key_1"
Using "ImpParty"

-- Gameplay
Using "ActivePowerHighlight"
Using "BanishedFxDark"
Using "BanishedFxLight"
Using "StaminaRestoredFront"
Using "StaminaRestoredBack"
Using "SkillProcFeedbackFx"
Using "AuraEdgeDeathProximity"
Using "GreaseSelfDestructPuddle"

-- Mastery FX
Using "SpeedUpBack"
Using "SpeedUpFront"
Using "RuneCircle02Trap"
Using "TeamReviveIconFrame"
Using "TeamReviveIconFrameColor"

-- Meteor and Bonfire
Using "Meteorite"
Using "BonfireIgnite"
Using "BonfireDouse"
Using "BonfireDousedA"
Using "BonfireDousedB"
Using "BonfireDousedSecondary"
Using "BonfireDoused"
Using "BonfireLowDouse"
Using "BonfireCriticalDouse"
Using "BonfireDousedFlare"
Using "BallDropShockwave"
Using "BallDropFlare"
Using "InkBallEmpty"

Using "ScoreFxBright"
Using "ScoreFxBright_Low"
Using "ScoreFxBright_High"

-- Team Frames
Using "RitesFrameNightwings01"
Using "RitesFrameFate01"

-- GoalDecalSigils
Using "NightwingsGrill01"
Using "AccusersGrill01"
Using "BeyondersGrill01"
Using "ChastityGrill01"
Using "DissidentsGrill01"
Using "EssenceGrill01"
Using "FateGrill01"
Using "PyreheartsGrill01"
Using "TempersGrill01"
Using "TrueNightwingsGrill01"
Using "WithdrawnGrill01"

-- Temp UI Sigils for PSX
Using "SigilNightwingsUI"
Using "SigilAccusersUI"
Using "SigilBeyondersUI"
Using "FacetsLightAnimRotate"
Using "FacetsA"


-- Match Objective Boxes
--Using "MatchObjective_CheckboxEmpty"
--Using "MatchObjective_CheckboxCheck"
--Using "MatchObjective_CheckboxBackground"
Using "MatchObjectiveBGIn"
Using "MatchObjectiveBGOut"

-- Weather FX
Using "LightningBolt"

-- Masteries
Using "PoisonPool01"

-- Respawn UI / Cams
Using "Auden_Blank_01"
Using "MapBG01"
Using "TeamWipedHUDPulse"
Using "ThrowDamageIndicatorBacking"

-- Goal Presentation
Using "ScoreSplatDecal"
Using "Sun01"
Using "Grain01"
Using "Facets"
Using "FacetsLight"

-- Meteor
Using "PreMeteor"
Using "DraftSwitchFx"
Using "DraftChoose"

-- Tutorial
Using "ArrowIndicator4"

-- Imp / Rewards
Using "ImpHopStatic"

-- Stars in the Sky
Using "StarNodeActiveStreakSpawner"
Using "StarMapStar"
Using "StarConnector"
Using "EclipseHalo01"

-- Victory Sequence
Using "SparkleFlakesRadial"

-- Weapons
Using "DebuffBack"
Using "DebuffFront"
Using "BanishFxFront"
Using "BanishFxBack"
Using "LobberReticle"
Using "BlackHoleFx"
Using "LightningBolt"
Using "GroundImpactDust"
Using "PyrePlungeDark"
Using "AuraExplosion"
Using "CollisionFx"
Using "CollisionFxBall"
Using "SlowDownRingBack"
Using "InvincibubbleTimer"

Using "ScoreBuffBack"
Using "ScoreBuffFront"

-- Archetypes - To be loaded based on character's chosen
Using "PlayerSmall"
Using "PlayerMedium"
Using "PlayerMediumAlt"
Using "PlayerLarge"
Using "PlayerImp"

-- Extra Animations
Using "PlayerSmallHowlTaunt"
Using "PlayerSmallScorePose"
Using "PlayerMediumAuraTurnOn"
Using "PlayerMediumScorePose"
Using "PlayerMediumAltScorePose"
Using "PlayerLargeScorePose"
Using "PlayerOralechScorePose"
Using "PlayerIgnariusScorePose"
Using "PlayerOralechAuraTurnOn"
Using "PlayerLargeAuraTurnOn"
Using "PlayerImpAuraTurnOn"
Using "PlayerLargeRun"

Using "PlayerSmallScorePoseAlt"
Using "PlayerMediumScorePoseAlt"
Using "PlayerMediumAltScorePoseAlt"
Using "PlayerLargeScorePoseAlt"
Using "PlayerImpScorePoseAlt"
Using "PlayerFlyingScorePoseAlt"
Using "PlayerMonsterScorePoseAlt"

--[[ * SETUP * ]] --

PHANTOM_OUTLINE_WIDTH = 1.25
PHANTOM_GRADIENT_TOP = 0.5
PHANTOM_GRADIENT_BOTTOM = 0.95

function SetupMatch()

	SetConfigOption({ Name = "AIMaxSearchDepth", Value = 64 })
	SetConfigOption({ Name = "UseOcclusion", Value = true })
	SetConfigOption({ Name = "ShowOffscreenIndicators", Value = false })
	SetConfigOption({ Name = "AllowUnitInput", Value = true })

	inMatch = true

	if SeasonMatchSitesPlayed ~= nil and currentNodeId ~= nil then
		SeasonMatchSitesPlayed[currentNodeId] = true
		LastWeekMatchSitePlayed[currentNodeId] = PlayerTeam.MatchesPlayed
	end

	ToggleVocalsState( "match" )

	local TokenGroup = GetIdsByType({ Name = "Ball01" })
	AddToGroup({ Ids = TokenGroup, Name = "Token" })

	TeamAObjectIds = GetIdsByType({ Name = "_PlayerUnit" })
	TeamAObjectIdsLookup = ToLookup( TeamAObjectIds )
	TeamBObjectIds = GetIdsByType({ Name = "_PlayerUnit2" })
	TeamBObjectIdsLookup = ToLookup( TeamBObjectIds )
	AddToGroup({ Ids = TeamAObjectIds, Name = "TeamA" })
	AddToGroup({ Ids = TeamBObjectIds, Name = "TeamB" })

	TeamObjectIds =
	{
		[1] = TeamAObjectIds,
		[2] = TeamBObjectIds
	}

	local goalAIds = GetIdsByType({ Name = "GoalAButton" })
	local goalBIds = GetIdsByType({ Name = "GoalBButton" })
	AddToGroup({ Ids = goalAIds, Name = "GoalAButtonGroup" })
	AddToGroup({ Ids = goalBIds, Name = "GoalBButtonGroup" })

	local EventsGroup = GetIds({ Name = "Events" })
	local CelebrationGroup = GetIds({ Name = "Celebration" })
	local CameraStart = GetIds({ Name = "CameraStart" })
	ballKickOffPosition = SpawnObstacle({ Name = "InvisibleTarget", DestinationId = ballId, Group = "Scripting" })

	ballId = TokenGroup[1]
	celebrationPoint = CelebrationGroup[1]
	EventPoints = EventsGroup
	goalA = goalAIds[1]
	goalB = goalBIds[1]

	cameraStart = CameraStart[1]
	local combatBorders = GetIds({ Name = "CombatArenas" })
	CreateAreaWalls({ Id = combatBorders[1], MapArea = "VirusMapArea" })

	StopSound({ Name = "/SFX/World Sounds/CaravanAmbience1", Duration = 0.5 }) -- for cheating issues

	SetOpacity({ Id = ballId, Fraction = 0 })

	KioskModeAutoRestart()

	CheckMatchType()

end

function CheckMatchType()
	local commonMatchExceptions = { "ScenarioDefense", "ScenarioRetrieval", "ScenarioBrawl" }
	local currentMap = GetMapName({ })
	if Contains( commonMatchExceptions, currentMap ) then
		uncommonMatch = true
	end
end

function SetupMatchSky()
	if introMatch or practiceMatch or uncommonMatch then
		return
	end

	thread(PickMoonPhase)

	if GetMapName({ }) ~= "MatchSiteI" then
		thread(StarSkyPresentationInstant)
	end
end

function ResetTimesBanished()
	for k, character in pairs( TeamA.TeamBench ) do
		character.TimesBanishedLatestRite = 0
	end
	for k, character in pairs( TeamB.TeamBench ) do
		character.TimesBanishedLatestRite = 0
	end
end

function SetupChallenge()
	ChallengeCharacter = GetCharByRef( ChallengeMatchCharRef )
	if ChallengeCharacter ~= nil and MatchChallenges ~= nil then
		ChallengeData = MatchChallenges[ChallengeCharacter.Archetype]
	end
end


-- MATCH START FUNCTION

OnAnyLoad{ function( triggerArgs )

	-- Resync the cached value
	LocalMP = GetConfigOptionValue({ Name = "LocalMP" })
	if ArcadeMode then
		SetupArcadeMatch()
	end

	if IsAscensionWeek() then
		--FadeOut({ Color = Color.White, Duration = 0 }) -- didn't work with map load color
		FadeOut({ Color = Color.Black, Duration = 0 })
	else
		FadeOut({ Color = Color.Black, Duration = 0 })
	end
	SetupMatch()
	SetupChallenge()

	SetConfigOption({ Name = "GamepadCursorControl", Value = false })
	SetConfigOption({ Name = "CameraFollowCursor", Value = 0.0 })
	SetConfigOption({ Name = "CameraEdgePanSpeed", Value = 0.0 })
	SetConfigOption({ Name = "CameraEdgePanThreshold", Value = 0.0 })
	SetConfigOption({ Name = "RallyPointAnimation", Value = "MoveHint" })
	SetConfigOption({ Name = "PlayerAIEvaluateTaunt", Value = true })
	hideEntireUI()
	DisableInput({ PlayerIndex = 1 })
	DisableInput({ PlayerIndex = 2 })
	if IsMultiplayerMatch() or introMatch or uncommonMatch or ChallengeData ~= nil or practiceMatch or GetConfigOptionValue({ Name = "UseCampaignShortMatchIntro" }) then
		thread( ShowInputDisabledPresentation )
	else
		thread( ShowInputDisabledPresentation, "StartBlindsClosed" )
	end

	PreLoadBinks({ Names = { "MeteoriteGround", "MeteoriteAir", "MeteoriteAirDark", "BallDropFlare", "BallDropShockwave" } })

	if introMatch then
		SetConfigOption({ Name = "PlayerAIEvaluateTaunt", Value = false })
		defaultMatchBloom = "Off"
		AdjustFullscreenBloom({ Name = "Off", Duration = 0.1 })
		PreLoadBinksForCharacter( CharBadass )
		PreLoadBinksForCharacter( CharFriend )
		PreLoadBinksForCharacter( CharJoker )
	elseif not practiceMatch then
		cameFromFreePlayMatch = false
		PersistVariable({ Name = "cameFromFreePlayMatch" })
	elseif practiceMatch then
		defaultMatchBloom = "Off"
		AdjustFullscreenBloom({ Name = "Off", Duration = 0.1 })
	else
		defaultMatchBloom = "Off"
	end

	if IsFirstAccusersMatch() then
		PreLoadBinksForCharacter( CharBadass )
		PreLoadBinksForCharacter( CharFriend )
		PreLoadBinksForCharacter( CharJoker )
	end

	thread( ResumeDefaultMatchColorGrade )

	SetupMatchSky()
	HideMatchUI()
	local fadeOutTime = 0.25
	FadeOutAllAmbience( fadeOutTime )
	StopSound({ Id = goalAFireAmbientLoopId, Duration = fadeOutTime })
	StopSound({ Id = goalBFireAmbientLoopId, Duration = fadeOutTime })

	if uncommonMatch then
		return
	end

	if netMPMatch then
		LockCamera({ Id = ballId })
		AdjustZoom({ Fraction = 0.525, LerpTime = 0.1, Duration = 9999 })
		return
	end

	if not IsMultiplayerMatch() and campaign then
		thread( CampaignMatchStartVO )
		PickCampaignMatchTeams()
		HideLocalMPDemoGroups()
		CheckRivalryMatch()
		CheckForfeitMatch()
		ResetTimesBanished()
		if GetConfigOptionValue({ Name = "UseCampaignShortMatchIntro" }) then
			CampaignShortMatchIntroPresentation()
		else
			MatchIntroPresentation()
			if not practiceMatch then
				SaveCheckpoint({ SaveName = "_StartRite", SuspendNonPersisted = true, ShowSaveSpinner = false, DevSaveName = CreateStartMatchDevSaveName() })
			end
		end
		SetConfigOption({ Name = "UseCampaignShortMatchIntro", Value = false })
		CommenceDraft( TeamA, TeamB )
		return
	end

	-- LocalMP
	-- For debug DirectLoad
	if PlayerTeam == nil then
		CreateLeague()
	end
	SetMultiplayerMatchOn()
	introMatch = false
	practiceMatch = false
	ChallengeData = nil
	ChallengeCharacter = nil
	forfeitMatch = false
	lastSeasonMatch = false
	rivalryMatch = false
	blockMatchVO = false

	if GetConfigOptionValue({ Name = "LocalMPReady" }) or ArcadeMode then
		StartLocalMP()
	else
		SetMultiplayerMatchOff()
		OpenMenu({ Name = "PauseScreen", StyleXML = "MainMenuScreen" })
		SetMenuOptions({ Name = "PauseScreen", Item = "ExitCampaignButton", Properties = { IsVisible = false } })
		SetMenuOptions({ Name = "PauseScreen", Item = "CampaignButton", Properties = { HelpTextId = "PauseScreen_CampaignMode", ActivateFunction = "MenuCampaignMode" } })
		OpenMenu({ Name = "LocalMPScreen" })
		if ConsecutiveLocalMPMatches ~= nil and ConsecutiveLocalMPMatches > 0 then
			-- thread( RematchPromptAudio )
		end
	end

end
}

function StartLocalMP()

	SetSoundCueValue({ Names = { "Misc", }, Id = LocalMPMusicId, Value = 0, Duration = 1.0 })
	SetSoundCueValue({ Names = { "Drums", }, Id = LocalMPMusicId, Value = 1, Duration = 0.3 })

	local playerTeamIndex = DebugDraftPlayerTeamIndex or 12
	local opposingTeamIndex = DebugDraftOpposingTeamIndex or 13
	if DebugDraftTeamALastDraftPicks ~= nil then
		TeamALastDraftPicks = DebugDraftTeamALastDraftPicks
	end
	if DebugDraftTeamBLastDraftPicks ~= nil then
		TeamBLastDraftPicks = DebugDraftTeamBLastDraftPicks
	end
	if DebugDraftLocalMPIntroPresentation ~= nil then
		SetConfigOption({ Name = "LocalMPIntroPresentation", Value = DebugDraftLocalMPIntroPresentation })
	end
	if DebugDraftLocalMP ~= nil then
		if DebugDraftLocalMP then
			SetMultiplayerMatchOn()
		else
			SetMultiplayerMatchOff()
		end
	end

	-- Hide PSX demo assets
	HideLocalMPDemoGroups()

	SetConfigOption({ Name = "LocalMPReady", Value = false })
	head2head = GetConfigOptionValue({ Name = "LocalMPHumanTeamA" }) and GetConfigOptionValue({ Name = "LocalMPHumanTeamB" })
	PrepareLocalMPDraft( playerTeamIndex, opposingTeamIndex )
	LocalMPConstellationPicks()
	thread( LocalMPMatchStartVO )
	local introPresentation = GetConfigOptionValue({ Name = "LocalMPIntroPresentation" })
	if introPresentation == "LocalMP_Intro_Short" or ForceShortIntro then
		ShortMatchIntroPresentation()
	elseif introPresentation == "LocalMP_Intro_Long" then
		MatchIntroPresentation()
	else
		if TeamALastDraftPicks ~= nil then
			DoDraftPicks( TeamA, TeamALastDraftPicks )
		end
		if TeamBLastDraftPicks ~= nil then
			DoDraftPicks( TeamB, TeamBLastDraftPicks )
		end
		PlayMatchAmbience()
		FadeIn({ Duration = 0.3 })
	end
	ForceShortIntro = false
	if not TeamA.DonePicking or not TeamB.DonePicking then
		CommenceDraft( TeamA, TeamB )
	else
		CompleteDraft()
	end

end

function LocalMPConstellationPicks()

	if not IsMultiplayerConstellations() then
		SetGroupVisibility({ Names = TitanStarGroups, Visible = false })
		RestoreAttributes()
		return
	end

	if ForceShortIntro then
		SetGroupVisibility({ Names = TitanStarGroups, Visible = false })
		ConstellationsModifyPyres()
		return
	end

	-- All stars unlocked
	for starName, star in pairs( ConstellationStars ) do
		star.Available = true
		star.NewlyAvailable = false
	end
	-- Setup Constellation picks
	PresentNightSky( "Match" )
	local constellationCamera = cameraStart
	if ConstellationCenterOverride ~= nil then
		SetupConstellationVisuals( ConstellationCenterOverride )
		constellationCamera = ConstellationCenterOverride
		SetGroupVisibility({ Name = "Art_BackdropFall01", Visible = false })
	end

	inMatch = false
	LockCamera({ Id = constellationCamera, Duration = 0, OffsetY = -250 })
	FocusCamera({ Fraction = 0.4, Duration = 0.01 })
	local extraStarMapBeauty = GetGroupWithSubGroups({ Names = { "ScribeStars", "ArenaIcons", "DisappearingStars", "TitanStars" } })
	SetGroupVisibility({ Names = extraStarMapBeauty, Visible = false })
	local extraHideGroups = { "Art_CaveBack01_Anims", "Art_CaveBack01", "Art_CaveBack02", "Art_Rain", "Art_Standing01", "Art_ForegroundFlyers" }
	local backdropGroups = GetGroupWithSubGroups({ Name = "Art_Backdrop01" })
	if CurrentMapName == "MatchSiteJ" then
		SetGroupVisibility({ Names = extraHideGroups, Visible = false })
		SetGroupVisibility({ Names = backdropGroups, Visible = false })
	end
	SetConfigOption({ Name = "AllowUnitInput", Value = false })
	FadeIn({ Duration = 0.3 })
	thread( DifficultyModifiersIntroAudio )
	CreateConstellations()
	-- Restore regular match state
	DisableInput({ })
	inMatch = true
	wait(1.0)
	FadeOut({ Color = Color.Black, Duration = 0.5 })
	wait(1.0)
	FadeAllConstellations( 0.0 )
	SetConfigOption({ Name = "AllowUnitInput", Value = true })
	AdjustColorGrading({ Name = "Off", Duration = 0 })
	ConstellationsModifyPyres()

	if ConstellationCenterOverride ~= nil then
		HideEntireNightSky()
		SetGroupVisibility({ Name = "Art_BackdropFall01", Visible = true })
	end
	if CurrentMapName == "MatchSiteJ" then
		SetGroupVisibility({ Names = extraHideGroups, Visible = true })
		SetGroupVisibility({ Names = backdropGroups, Visible = true })
	end

end

function HideLocalMPDemoGroups()
	local showcaseGroups = GetGroupWithSubGroups({ Name = "Art_LocalMP" })
	SetGroupVisibility({ Names = showcaseGroups, Visible = false })
	SetGroupVisibility({ Name = "Art_LocalMP03", Visible = false })
	SetGroupVisibility({ Name = "Moon01", Visible = false })
end

function PostDraftComplete()

	if not uncommonMatch and not introMatch then
		PanCamera({ Id = ballId, Duration = 1.25, EaseOut = 0.25, OffsetY = -150 })
		FocusCamera({ Fraction = 0.6, Duration = 1.25, ZoomType = "Ease" })
	end

	if teachFlying and not flyingTaught and not practiceMatch and not IsMultiplayerMatch() then
		flyingTaught = true
		PersistVariable({ Name = "flyingTaught" })
		fancyCamera = false
		thread(CommenceFlightSchool)
	else
		StartMatch()
	end
end

function StartMatch()

	AddInputBlock({ Name = "StartMatch", PlayerIndex = 1 })
	AddInputBlock({ Name = "StartMatch", PlayerIndex = 2 })

	-- audio
	FadeOutAmbience( 15.0 )

	notify("PreMeteorAboutToStart") -- for MatchSiteJ imps
	if IsMultiplayerMatch() then
		PlayOverheadAnimation({ Name = "PreMeteor", DestinationId = ballId, IgnoreOverheadOffset = true, OffsetY = -400 })
	elseif matchesPlayed == 0 then
		PlayOverheadAnimation({ Name = "PreMeteor", DestinationId = ballId, IgnoreOverheadOffset = true, OffsetY = -400 })
	else
		wait(0.925)
		PlayOverheadAnimation({ Name = "PreMeteor", DestinationId = ballId, IgnoreOverheadOffset = true, OffsetY = -400 })
	end
	wait(1.9)

	matchStarted = true

	if not uncommonMatch then
		thread(MeteoriteSlam, ballId, nil, 0.2, true )
		SetOpacity({ Id = ballId, Fraction = 1, Duration = 0.1, Delay = 0.35 })

		if CurrentMapName == "MatchSiteI" then
			thread( ScribePillars, 1.2 )
		elseif CurrentMapName == "MatchSiteF" then
			SwitchActiveUnit({ Id = 190006, PlayerIndex = 2 })
		elseif CurrentMapName == "MatchSiteE" then
			SwitchActiveUnit({ Id = 190005, PlayerIndex = 2 })
		end

		if ChallengeData ~= nil then
			PickPreSetRuneCover( ChallengeData.CoverLayout )
			TurnOnUnitAurasInChallenge( )
		elseif CurrentMapName == "MatchSiteA" then
			if practiceMatch then
				thread( PickPreSetRuneCover )
			elseif IsMultiplayerMatch() then
				thread( PickPreSetRuneCover, "CoverRunes01" )
			end
		end
	end

	wait(0.75)

	if fancyCamera then
		MatchCameraSelector( "fancyNewRound" )
		-- Set zoom clamps
		SetConfigOption({ Name = "CameraMinZoom", Value = fancyCameraData.CamZoomMin })
		SetConfigOption({ Name = "CameraMaxZoom", Value = fancyCameraData.CamZoomMax })
	end
	SetCameraClamp({ Ids = { CameraBorderObjectIds[1], CameraBorderObjectIds[2] }, LerpTime = 1.0 })
	DynamicZoomCamera({ Enabled = true, Speed = 0.05 })

	if not introMatch and not uncommonMatch then
		initialUIShown = true
		ShowMatchUI()
	end

	if not firstMatch and not uncommonMatch and not netMPMatch then
		EnableInput({ PlayerIndex = 1 })
		EnableInput({ PlayerIndex = 2 })
		thread(HideInputDisabledPresentation)
	end

	RemoveInputBlock({ Name = "StartMatch", PlayerIndex = 1 })
	RemoveInputBlock({ Name = "StartMatch", PlayerIndex = 2 })

	if practiceMatch and ChallengeData == nil then
		thread( DisplayObjective, "StartFreeplayMatch" )
	end

	local activeEvent = CheckMidMatchEvents()
	if activeEvent == nil then
		PlayMatchDialogue( "matchstart" )
	end

	if IsMultiplayerMatch() or IsFirstAccusersMatch() then
		thread( MatchStartAudio )
	end

	CheckAuraWithBallCurse( TeamA )
	CheckAuraWithBallCurse( TeamB )
	CheckPairPowerUp( TeamA )
	CheckPairPowerUp( TeamB )

	if IsMultiplayerMatch() then
		SetRichPresence({ Name = "RP_LocalMPPresence" })
	elseif ChallengeData ~= nil then
		SetRichPresence({ Name = "RP_InMatchSiteA_Challenge" })
	elseif practiceMatch then
		SetRichPresence({ Name = "RP_InMatchSiteA_FreePlay" })
	else
		SetRichPresence({ Name = "RP_In"..CurrentMapName })
	end

	-- Start AI
	if TeamAIs[TeamB.LeagueIndex] ~= nil then
		thread( MatchAI, TeamAIs[TeamB.LeagueIndex] )
	else
		thread( MatchAIAdaptive )
	end
end

function GetIntroPanDuration()

	local MatchIntroPans =
	{
		-- search for 'SeasonSchedule =' and look for notes below that for details on specific matches
		-- season 1
		[0] = { PanTime = 35, PanEaseInDuration = 5 },
		[1] = { PanTime = 36.5, PanEaseInDuration = 5 },
		[2] = { PanTime = 30 },
		[3] = { PanTime = 31 },
		[4] = { PanTime = 33 },
		[5] = { PanTime = 37 },
		[6] = { PanTime = 33 },
		-- ascension match 1
		[7] = { PanTime = 33 },

		-- season 2; PlayerTeam.AscensionMatchesPlayed == 1
		[8] = { PanTime = 30 },
		[9] = { PanTime = 22 },
		[10] = { PanTime = 31 },
		-- ascension match 2
		[11] = { PanTime = 33 },

		-- season 3; PlayerTeam.AscensionMatchesPlayed == 2
		[12] = { PanTime = 29 },
		[13] = { PanTime = 30 },
		[14] = { PanTime = 30 },
		-- ascension match 3
		[15] = { PanTime = 33 },

		-- season 4; PlayerTeam.AscensionMatchesPlayed == 3
		[16] = { PanTime = 35 },
		[17] = { PanTime = 26 },
		-- ascension match 4
		[18] = { PanTime = 33 },

		-- season 5; PlayerTeam.AscensionMatchesPlayed == 4
		[19] = { PanTime = 28 },
		[20] = { PanTime = 40 },
		-- ascension match 5
		[21] = { PanTime = 37 },

		-- season 6; PlayerTeam.AscensionMatchesPlayed == 5
		[22] = { PanTime = 30 },
		-- ascension match 6
		[23] = { PanTime = 35 },

		-- season 7; PlayerTeam.AscensionMatchesPlayed == 6
		[24] = { PanTime = 40 },
		-- ascension match 7
		[25] = { PanTime = 60 },
	}

	if ChallengeData ~= nil then
		return 4.4
	elseif practiceMatch then
		return 5
	elseif IsMultiplayerMatch() then
		return 20
	elseif introMatch then
		return 40
	elseif MatchIntroPans[matchesPlayed] ~= nil then
		return MatchIntroPans[matchesPlayed].PanTime
	elseif allowCaravanFly then
		return 24
	end

	return 30
end

function CheckForfeitMatch()
	if IsForfeitMatch() then
		TeamB.Forfeited = true
		forfeitMatch = true
		SetMenuOptions({ Name = "InGameUI", Properties = { Run = "HideTeamBPortraits" } })
		-- persisted to allow for post-match events; set false in Campaign
		PersistVariable({ Name = "forfeitMatch" })
		SetOpacity({ Id = TeamB.GoalSigilId, Fraction = 0 })
	end
end

function CheckRivalryMatch()
	if IsRivalryPresent() then
		rivalryMatch = true
		-- persisted to allow for post-match events; set false in Campaign
		PersistVariable({ Name = "rivalryMatch" })
	end
end

function HidePyres()
	StopAnimation({ DestinationIds = { TeamA.PyreId, TeamB.PyreId } })
	StopAnimation({ DestinationId = TeamA.PyreId, PreventChain = true })
	StopAnimation({ DestinationId = TeamB.PyreId, PreventChain = true })
	SetOpacity({ Id = TeamA.PyreId, Fraction = 0, Duration = 0 })
	SetOpacity({ Id = TeamB.PyreId, Fraction = 0, Duration = 0 })
end

OptimizationHideGroups = {
	["MatchSiteB"] = { "Art_Backdrop00", "Art_Backdrop05_1.4", "Art_BackdropAtmos02_1.4", "Art_Backdrop04", "Art_Backdrop03", "Art_Backdrop02", "Art_Backdrop01", "Art_BackdropAtmos01" },
	["MatchSiteC"] = { "Moon01", "Art_Terrain04", "Art_Terrain04", "Art_Atmos02", "Art_ScorpionBody01", "Art_ScorpionTail01", "Art_Terrain02", "Art_RiverUnder01", "Art_RiverRocks01", "Art_River01", "Art_Terrain00", "Art_Atmos05" },
	["MatchSiteD"] = { "Art_Shax01", "Art_Shax02", "Art_Shax03", "Backdrop_Anims01", "Backdrop_Terrain02", "BackdropCelestial_01", "Backdrop_Terrain03" },
	["MatchSiteE"] = { "Art_Sky", "Art_MonsterBack_01_P_14", "Art_MonsterBack_01_P_13", "Art_Monster0_P_12", "Art_Monster02_X"},
	["MatchSiteF"] = { "Art_Plurnes_01" },
	["MatchSiteG"] = { "Art_Cliffs01", "Art_Xilvas00", "Art_Xilvas01", "Art_Xilvas02", },
	["MatchSiteH"] = { "Art_ThornBush01", "Art_Background01_Lighting", "Art_BackdropVines01", "Art_Shalmot01", "Art_LuTree02_X_145", "Art_LuTree01", "Art_Shalmot03"},
	["MatchSiteI"] = { "Art_BackdropAtmos00", "Art_BackdropFall01_X", "Art_BackdropScribe03", "Art_Sigils01", "Art_BackdropScribe02", "Art_BackdropScribe01" },
	["MatchSiteJ"] = { "Backdrop03", "Art_CaveBack01", "Art_CaveBack02", "Art_Rain" },
}

OptimizationDeleteIds =
{
	["MatchSiteA"] = { 210166 },
}

function HideOffscreenMatchGroups()
	DebugPrint({ Text = "Hiding off-screen match groups" })

	local map = GetMapName({ })

	local entireNightSky = GetGroupWithSubGroups({ Name = "NightSky" })
	SetGroupVisibility({ Names = entireNightSky, Visible = false })
	SetOpacity({ Names = entireNightSky, Fraction = 0 })

	local entireDifficultyStars = GetGroupWithSubGroups({ Name = "DifficultyStars" })
	SetGroupVisibility({ Names = entireDifficultyStars, Visible = false })

	if not OptimizationHideGroups[map] then
		return
	end

	SetOpacity({ Names = OptimizationHideGroups[map], Fraction = 0, Duration = 0.3, Delay = 0 })
	Delete({ Ids = OptimizationDeleteIds[map] })
	wait(0.3)
	SetGroupVisibility({ Names = OptimizationHideGroups[map], Visible = false })
end

function ShowOptimizedMapGroups()
	local map = GetMapName({ })
	SetGroupVisibility({ Names = OptimizationHideGroups[map], Visible = true })
	SetOpacity({ Names = OptimizationHideGroups[map], Fraction = 1.0, Duration = 0.3, Delay = 0 })
end

function MatchIntroPresentation()

	matchOpeningSequence = true

	local panDuration = GetIntroPanDuration()
	local panEaseInDuration = 2
	if introMatch or matchesPlayed == 1 then
		panEaseInDuration = 5
	end

	local mapIntroPresentationStartingZoom = 0.325
	local mapPreDraftZoom = 0.425
	local currentMatchSite = GetMapName({ })
	local MatchIntroPresentationData =
	{
		["MatchSiteA"] = { CameraOffsetY = 0, PanDistribution = "Type01" },
		["MatchSiteB"] = { CameraOffsetY = 0, StartZoom = 0.56, PanDistribution = "Type01" },
		["MatchSiteC"] = { CameraOffsetY = 0, PanDistribution = "Type02" },
		["MatchSiteD"] = { CameraOffsetY = 0, PanDistribution = "Type02" },
		["MatchSiteE"] = { CameraOffsetY = 0, PanDistribution = "Type02" },
		["MatchSiteF"] = { CameraOffsetY = 0, PanDistribution = "Type02" },
		["MatchSiteG"] = { CameraOffsetY = 0, PanDistribution = "Type02" },
		["MatchSiteH"] = { CameraOffsetY = 0, PanDistribution = "Type02" },
		["MatchSiteI"] = { CameraOffsetY = 0, PanDistribution = "Type02", PreDraftZoom = 0.325 },
		["MatchSiteJ"] = { CameraOffsetY = 0, PanDistribution = "Type02" },
	}

	local matchIntroData = MatchIntroPresentationData[currentMatchSite]
	if matchIntroData.PreDraftZoom ~= nil then
		mapPreDraftZoom = matchIntroData.PreDraftZoom
	end

	local bonfiresInView = ( 2 * panDuration / 3 )
	local cameraSettledOnField = ( panDuration / 3 )
	if matchIntroData.PanDistribution == "Type02" then
		DebugPrint({ Text = "Using Type02 Pan" })
		bonfiresInView = ( 4 * panDuration / 5 )
		cameraSettledOnField = ( panDuration / 5 )
		panDuration = panDuration * 1.35 -- extend pan time for camera-only and not the waits
	else
		DebugPrint({ Text = "Using Type01 Pan" })
	end

	local extraStarMapBeauty = GetGroupWithSubGroups({ Names = { "ScribeStars", "ArenaIcons", "DisappearingStars", "TitanStars" } })
	SetGroupVisibility({ Names = extraStarMapBeauty, Visible = false })
	PresentNightSky( "Match" )

	wait(0.30)

	-- camera
	ClearCameraClamp({  })
	AdjustZoom({ Fraction = mapIntroPresentationStartingZoom, LerpTime = 0.0001, Duration = 999999 })
	AdjustColorGrading({ Name = "Off", Duration = 0 })
	thread(ResumeDefaultMatchColorGrade, panDuration)

	-- audio
	PlayMatchAmbience()

	-- pans
	local newCameraLockPoint = SpawnObstacle({ Name = "InvisibleTarget", Group = "IntroHelpers", DestinationId = ballId })
	local panParams = { Id = newCameraLockPoint, Duration = panDuration, OffsetY = 0, Delay = 0.2, EaseIn = panEaseInDuration, EaseOut = 3 }
	if introMatch then
		LockCamera({ Id = cameraStart, Duration = 0, Delay = 0, OffsetY = -220 })
		panParams.Duration = panDuration * 1.3
		AdjustFullscreenBloom({ Name = "SaturatedLight", Duration = 0.2 })
		AdjustFullscreenBloom({ Name = "Off", Duration = 5, Delay = 4 })
		AdjustFullscreenBloom({ Name = "Subtle", Duration = 2, Delay = 10 })
		AdjustFullscreenBloom({ Name = "Off", Duration = 5, Delay = panDuration })
	elseif practiceMatch then
		AdjustZoom({ Fraction = 0.330, LerpTime = 0.0001, Duration = 999999 })
		LockCamera({ Id = 50143, Duration = 0, OffsetY = 0 })
		panParams.EaseIn = 0
	elseif matchIntroData ~= nil then
		LockCamera({ Id = cameraStart, Duration = 0, OffsetY = matchIntroData.CameraOffsetY - 250 }) -- the 180 is parallax correction
	else
		LockCamera({ Id = cameraStart, Duration = 0, Delay = 0, OffsetY = -600 })
	end

	wait(0.05)
	if GetMapName({ }) == "MatchSiteA" and not IsMultiplayerMatch() then
		thread( FullscreenFadeIn, "Normal", "Overlay" )
	else
		FadeIn({ Duration = 0.3 })
		if not introMatch and ChallengeData == nil and not IsMultiplayerMatch() then
			OpenBlinds( 0.11 )
		end
	end

	PanCamera( panParams )
	FocusCamera({ Fraction = mapPreDraftZoom, Duration = panDuration * 1.3, Delay = 2, ZoomType = "Ease" })

	-- bonfires & teams setup
	HidePyres()

	SetOpacity({ Ids = TeamAObjectIds, Fraction = 0 })
	SetOpacity({ Ids = TeamBObjectIds, Fraction = 0 })

	-- Teleport players to starting positions
	TeleportToStartingPositions()

	-- First Accusers Match
	if IsFirstAccusersMatch() then
		DisarmAuras()
		thread(SpecialMatchIntroEntrances2, "Rukey", TeamAObjectIds[3], 19.9)
		thread(SpecialMatchIntroEntrances2, "Hedwyn", TeamAObjectIds[1], 17.1)
		thread(SpecialMatchIntroEntrances2, "Jodi", TeamAObjectIds[2], 17.3)

		thread(SpecialMatchIntroEntrances2, "Lendel", TeamBObjectIds[1], panDuration - 8.0)
		thread(SpecialMatchIntroEntrances2, "Accusers", TeamBObjectIds[2], panDuration - 8.7)
		thread(SpecialMatchIntroEntrances2, "Accusers", TeamBObjectIds[3], panDuration - 8.1)

	-- Challenge Match
	elseif ChallengeData ~= nil then
		local fiddlyIntroPresentationConstant = 4

		thread(SpecialMatchIntroEntrances, ChallengeData.MatchEntrance, TeamAObjectIds[1], fiddlyIntroPresentationConstant + 0.3, "NoCamera", "UseDraftVFX" )
		thread(SingleBeamEntrance, TeamBObjectIds[1], fiddlyIntroPresentationConstant + 1)

		thread( MeteoriteSlam, goalA, TeamA.PyreId, fiddlyIntroPresentationConstant - 1, nil, TeamA.MeteorColorGrade )
		thread( ShowGoalHealth, TeamA, "PlayerIntro", fiddlyIntroPresentationConstant - 1 )
		thread( MeteoriteSlam, goalB, TeamB.PyreId, fiddlyIntroPresentationConstant + 1, nil, TeamB.MeteorColorGrade )
		thread( ShowGoalHealth, TeamB, "OpponentIntro", fiddlyIntroPresentationConstant + 1 )
	end

	wait(bonfiresInView)

	-- for events that happen on the way down to the settling

	if introMatch then
		local fireZoom = GetCameraZoom({ })
		PanCamera({ Id = 50004, OffsetX = 0, Duration = 1.8, EaseIn = 0.1, EaseOut = 0.3, Delay = cameraSettledOnField + 0.45 })
		FocusCamera({ ZoomType = "Overshoot", Relative = false, Duration = 0.6 * 1.5, Fraction = fireZoom * 1.15, Delay = cameraSettledOnField + 0.45 })
	end

	wait(cameraSettledOnField)

	thread(HideOffscreenMatchGroups)

	-- SFX: removes any sound generators during intro pan
	RemoveIntroAmbienceObjects()

	-- cameras
	if introMatch then
		local fireZoom = GetCameraZoom({ })
		PanCamera({ Id = goalA, Duration = 1.1, EaseIn = 0.2, EaseOut = 0.4, Delay = 2.25 })
		FocusCamera({ ZoomType = "Undershoot", Relative = false, Duration = 0.3, Fraction = fireZoom * 1.15, Delay = 2.45 })
	end

	-- meteor slams
	if introMatch then
		thread( MeteoriteSlam, goalA, TeamA.PyreId, 2.3, nil, TeamA.MeteorColorGrade )
		thread( MeteoriteSlam, goalB, TeamB.PyreId, 0.3, nil, TeamB.MeteorColorGrade )
	elseif IsForfeitMatch() then
		thread( MeteoriteSlam, goalA, TeamA.PyreId, 0.3, nil, TeamA.MeteorColorGrade )
		thread( ShowGoalHealth, TeamA, "PlayerIntro" )
	elseif ChallengeData ~= nil then
		-- Do nothing
	else
		thread( MeteoriteSlam, goalA, TeamA.PyreId, 0.3, nil, TeamA.MeteorColorGrade )
		thread( ShowGoalHealth, TeamA, "PlayerIntro" )
		thread( MeteoriteSlam, goalB, TeamB.PyreId, 2.3, nil, TeamB.MeteorColorGrade )
		thread( ShowGoalHealth, TeamB, "OpponentIntro" )
		if IsMultiplayerMatch() and not IsMultiplayerDraft() then
			-- Do picks
			thread( DoRandomDraftPicks, TeamA, 1.3 )
			thread( DoRandomDraftPicks, TeamB, 3.3 )
		end
	end

	if introMatch then
		local zoom = 0.6
		local fiddlyIntroPresentationConstant = 3

		thread(SpecialMatchIntroEntrances, "Rukey", TeamAObjectIds[2], fiddlyIntroPresentationConstant + 0.3)
		thread(SpecialMatchIntroEntrances, "Hedwyn", TeamAObjectIds[1], fiddlyIntroPresentationConstant + 0.6)
		thread(SpecialMatchIntroEntrances, "Jodi", TeamAObjectIds[3], fiddlyIntroPresentationConstant + 1.5)

		FocusCamera({ ZoomType = "Ease", Relative = false, Duration = 5, Fraction = zoom, Delay = fiddlyIntroPresentationConstant + 3 })

		wait(2.85)
	end

	if IsForfeitMatch() then
		wait(2)
		NarrationSequence( "OpenStructure_ForfeitMatch_Beginning_01" )
		wait(0.85)
		matchOpeningSequence = false
		return
	end

	if ChallengeData ~= nil then
		wait(1.85)
	elseif introMatch then
		wait(7.5)
	else
		wait(5.5)
	end

	PlayMatchDialogue( "prematch" )

	if ChallengeData == nil then
		wait(1)
		PreDraftTutorial()

		--PlayMatchDialogue( "draftstart" )

		if matchesPlayed ~= nil and matchesPlayed == 0 then
			wait(0.35)
		end
		if matchesPlayed ~= nil and matchesPlayed > 0 then
			--wait(0.35)
		end
	end

	if introMatch then
		PanCamera({ Id = 40001, Duration = 3, EaseIn = 0.2, EaseOut = 1, Delay = 0 })
	end

	matchOpeningSequence = false
end

function TeleportToStartingPositions()

	if introMatch then
		return
	end

	if IsMultiplayerMatch() or ChallengeData ~= nil then
		for k, characterData in pairs( TeamA.TeamBench ) do
			Teleport({ Id = characterData.ObjectId, DestinationId = StartingPositions[characterData.ObjectId], OffsetX = 0 })
		end
		for k, characterData in pairs( TeamB.TeamBench ) do
			Teleport({ Id = characterData.ObjectId, DestinationId = StartingPositions[characterData.ObjectId], OffsetX = 0 })
		end
	else
		for k, characterData in pairs( TeamA.TeamBench ) do
			Teleport({ Id = characterData.ObjectId, DestinationId = StartingPositions[characterData.ObjectId], OffsetX = -350 })
		end
		for k, characterData in pairs( TeamB.TeamBench ) do
			Teleport({ Id = characterData.ObjectId, DestinationId = StartingPositions[characterData.ObjectId], OffsetX = 350 })
		end
	end
end

function TeleportToExactStartPositions()
	for k, id in pairs( TeamA.ObjectIds ) do
		Teleport({ Id = id, DestinationId = StartingPositions[id] })
	end
	for k, id in pairs( TeamB.ObjectIds ) do
		Teleport({ Id = id, DestinationId = StartingPositions[id] })
	end
end

function ShortMatchIntroPresentation()

	AddInputBlock({ Name = "ShortMatchIntroPresentation", PlayerIndex = 1 })
	AddInputBlock({ Name = "ShortMatchIntroPresentation", PlayerIndex = 2 })

	PlayMatchAmbience()

	for k, characterData in pairs( TeamA.TeamBench ) do
		Teleport({ Id = characterData.ObjectId, DestinationId = StartingPositions[characterData.ObjectId], OffsetX = 0 })
	end

	for k, characterData in pairs( TeamB.TeamBench ) do
		Teleport({ Id = characterData.ObjectId, DestinationId = StartingPositions[characterData.ObjectId], OffsetX = 0 })
	end

	HidePyres()

	ClearCameraClamp({  })
	PanCamera({ Id = ballId, Duration = 0.1, OffsetY = -800, Delay = 0, EaseIn = 0.05, EaseOut = 0.05 })
	if CurrentMapName == "MatchSiteI" then
		FocusCamera({ Fraction = 0.325, Duration = 0.1, Delay = 0, ZoomType = "Ease" })
	else
		FocusCamera({ Fraction = 0.425, Duration = 0.1, Delay = 0, ZoomType = "Ease" })
	end
	thread(HideOffscreenMatchGroups)
	wait(0.2)
	PanCamera({ Id = ballId, Duration = 7, OffsetY = 50, Delay = 0, EaseIn = 0.05, EaseOut = 1, Retarget = true })

	wait(0.3)
	PlayMatchSpeech( "localmprematch" )
	wait(1.0)
	FadeIn({ Duration = 0.35 })
	wait(1)
	PlayMatchSpeech( "matchstart" )
	thread( MeteoriteSlam, goalA, TeamA.PyreId, 0.1, nil, TeamA.MeteorColorGrade )
	thread( ShowGoalHealth, TeamA, "PlayerIntro", 0.1 )
	wait(1.0)
	if TeamALastDraftPicks ~= nil then
		DoDraftPicks( TeamA, TeamALastDraftPicks )
		TeamALastDraftPicks = nil
	elseif not IsMultiplayerDraft() then
		DoRandomDraftPicks( TeamA )
	end
	wait(0.5)
	thread( MeteoriteSlam, goalB, TeamB.PyreId, 0.1, nil, TeamB.MeteorColorGrade )
	thread( ShowGoalHealth, TeamB, "OpponentIntro", 0.1 )
	wait(1.0)
	if TeamBLastDraftPicks ~= nil then
		DoDraftPicks( TeamB, TeamBLastDraftPicks )
		TeamBLastDraftPicks = nil
	elseif not IsMultiplayerDraft() then
		DoRandomDraftPicks( TeamB )
	else
		wait(1.0)
	end

	RemoveInputBlock({ Name = "ShortMatchIntroPresentation", PlayerIndex = 1 })
	RemoveInputBlock({ Name = "ShortMatchIntroPresentation", PlayerIndex = 2 })

end

function CampaignShortMatchIntroPresentation()

	AddInputBlock({ Name = "ShortMatchIntroPresentation", PlayerIndex = 1 })
	AddInputBlock({ Name = "ShortMatchIntroPresentation", PlayerIndex = 2 })

	PlayMatchAmbience()

	for k, characterData in pairs( TeamA.TeamBench ) do
		Teleport({ Id = characterData.ObjectId, DestinationId = StartingPositions[characterData.ObjectId], OffsetX = 0 })
	end

	for k, characterData in pairs( TeamB.TeamBench ) do
		Teleport({ Id = characterData.ObjectId, DestinationId = StartingPositions[characterData.ObjectId], OffsetX = 0 })
	end

	HidePyres()

	wait(0.01)
	ClearCameraClamp({  })
	PanCamera({ Id = ballId, Duration = 0.1, OffsetY = -800, EaseIn = 0.05, EaseOut = 0.05 })
	if CurrentMapName == "MatchSiteI" then
		FocusCamera({ Fraction = 0.325, Duration = 0.1, Delay = 0, ZoomType = "Ease" })
	else
		FocusCamera({ Fraction = 0.425, Duration = 0.1, Delay = 0, ZoomType = "Ease" })
	end
	thread(HideOffscreenMatchGroups)
	wait(0.2)
	PanCamera({ Id = ballId, Duration = 7, OffsetY = 50, EaseIn = 0.05, EaseOut = 1, Retarget = true })
	wait(0.3)
	FadeIn({ Duration = 0.35 })
	wait(1.0)
	thread( MeteoriteSlam, goalA, TeamA.PyreId, 0.1, nil, TeamA.MeteorColorGrade )
	thread( ShowGoalHealth, TeamA, "PlayerIntro", 0.1 )
	wait(1.0)
	thread( MeteoriteSlam, goalB, TeamB.PyreId, 0.1, nil, TeamB.MeteorColorGrade )
	thread( ShowGoalHealth, TeamB, "OpponentIntro", 0.1 )
	wait(1.0)

	RemoveInputBlock({ Name = "ShortMatchIntroPresentation", PlayerIndex = 1 })
	RemoveInputBlock({ Name = "ShortMatchIntroPresentation", PlayerIndex = 2 })

end

MatchSiteColorGradingDefaults =
{
	["MatchSiteA"] = { ColorGradeSetting = "Off" },
	["MatchSiteB"] = { ColorGradeSetting = "Off" },
	["MatchSiteC"] = { ColorGradeSetting = "Off" },
	["MatchSiteD"] = { ColorGradeSetting = "Off" },
	["MatchSiteE"] = { ColorGradeSetting = "Off" },
	["MatchSiteF"] = { ColorGradeSetting = "Off" },
	["MatchSiteG"] = { ColorGradeSetting = "Off" },
	["MatchSiteH"] = { ColorGradeSetting = "Off" },
	["MatchSiteI"] = { ColorGradeSetting = "Off" },
	["MatchSiteJ"] = { ColorGradeSetting = "Off" },

	["ScenarioRetrieval"] = { ColorGradeSetting = "Off" },
	["ScenarioBrawl"] = { ColorGradeSetting = "Off" },
	["ScenarioDefense"] = { ColorGradeSetting = "Off" },
	["Default"] = { ColorGradeSetting = "Off" },
}

function ResumeDefaultMatchColorGrade( duration, delay )
	if duration == nil then
		duration = 1
	end
	if delay ~= nil and delay > 0 then
		wait(delay)
	end

	local matchSite = GetMapName({ })
	local colorGradingDefault = MatchSiteColorGradingDefaults[matchSite] or MatchSiteColorGradingDefaults.Default
	AdjustColorGrading({ Name = colorGradingDefault["ColorGradeSetting"], Duration = duration })
end

function LocalMPShowcaseIntro()
	animatedLogoId1 = 40009
	leftStartSigil = nil
	rightStartSigil = nil
	if GetMapName({ }) == "MatchSiteC" then
		animatedLogoId1 = 50006
		leftStartSigil = 210410
		rightStartSigil = 210606
	end

	SetOpacity({ Ids = { leftStartSigil, rightStartSigil }, Fraction = 0, Duration = 0 })

	Activate({ Id = animatedLogoId1, Delay = 1 })
	Teleport({ Id = animatedLogoId1, DestinationId = cameraStart })

	HideInputDisabledPresentation()

	local matchTextOffsetY = 1200

	matchText1 = SpawnObstacle({ Name = "InvisibleTarget", Group = "Events", DestinationId = cameraStart, OffsetY = matchTextOffsetY, OffsetX = -700 })
	matchText1B = SpawnObstacle({ Name = "InvisibleTarget", Group = "Events", DestinationId = cameraStart, OffsetY = matchTextOffsetY + 150, OffsetX = -700 })
	matchText2 = SpawnObstacle({ Name = "InvisibleTarget", Group = "Events", DestinationId = cameraStart, OffsetY = matchTextOffsetY, OffsetX = 700 })
	matchText2B = SpawnObstacle({ Name = "InvisibleTarget", Group = "Events", DestinationId = cameraStart, OffsetY = matchTextOffsetY + 150, OffsetX = 700 })


	sigil1 = SpawnObstacle({ Name = "SigilNightwingsUI", DestinationId = matchText1, Group="Events", OffsetY = -250})
	sigil2 = SpawnObstacle({ Name = "SigilBeyondersUI", DestinationId = matchText2, Group="Events", OffsetY = -250})

	SetScale({ Fraction = 0.21, Ids = { sigil1, sigil2 }, Duration = 0 })
	SetColor({ Color = {32, 32, 32, 255}, Ids = { sigil1, sigil2 }, Duration = 0})
	SetOpacity({ Fraction = 0, Ids = { sigil1, sigil2 }, Duration = 0 })


	SetScale({ Fraction = 0.24, Id = sigil1, Duration = 1, Delay = 1 })
	SetScale({ Fraction = 0.24, Id = sigil2, Duration = 1, Delay = 1.25 })

	SetOpacity({ Fraction = 1, Id = sigil1, Duration = 1, Delay = 1 })
	SetOpacity({ Fraction = 1, Id = sigil2, Duration = 1, Delay = 1.25 })

	wait(1)
	SetFlagTrue({ Name = "AllowLocalMPStart", Delay = 1 })
	ShowMPShowcaseText( "KioskMode_LocalMPShowcase_P1", nil, matchText1, 0, "AlegreyaSCBold" )
	ShowMPShowcaseText( "KioskMode_LocalMPShowcase_01", nil, matchText1B, 0, "AlegreyaSCRegular" )
	ShowMPShowcaseText( "KioskMode_LocalMPShowcase_P2", nil, matchText2, 0, "AlegreyaSCBold"  )
	ShowMPShowcaseText( "KioskMode_LocalMPShowcase_01", nil, matchText2B, 0, "AlegreyaSCRegular"  )
	PlayOverheadAnimation({ Name = "DemoEndTextShadow", DestinationId = endScreenTextConstant, IgnoreOverheadOffset = true })

	waitUntil("CommenceLocalMPShowcase")
	thread(ShowInputDisabledPresentation)
	SetFlagFalse({ Name = "AllowLocalMPStart" })
	SetOpacity({ Id = showcaseBG, Fraction = 0, Duration = 0.32 })
	SetOpacity({ Fraction = 0, Ids = { sigil1, sigil2 }, Duration = 0.32 })
	--SetOpacity({ Ids = titleLogoIds, Fraction = 0.0, Duration = 0.5 })
	RemoveWorldText({ DestinationIds = { matchText1, matchText1B, matchText2, matchText2B }, Duration = 0.32 })
	Rumble({ RightFraction = 0.3, Duration = 0.335 })
	PlayAnimation({ Name = "PyreLogoOut", DestinationId = animatedLogoId1 })
	SetConfigOption({ Name = "AllowPause", Value = true, Delay = 0.5 })
	FadeOutAmbience( 5.0 )

	Move({ Names = { "Art_LocalMPLeft01", "Art_LocalMPLeft02" }, Speed = 95, Angle = 180 })
	Move({ Names = { "Art_LocalMPRight01", "Art_LocalMPRight02" }, Speed = 95, Angle = 0 })

	SetOpacity({ Names = { "Art_LocalMP00", "Art_LocalMP01", "Art_LocalMP02", "Art_LocalMP03" }, Fraction = 0, Duration = 0.6 })

	Stop({ Names = { "Art_LocalMPLeft01", "Art_LocalMPLeft02", "Art_LocalMPRight01", "Art_LocalMPRight02" }, Delay = 10  })

end

function ShowMPShowcaseText( matchText, delay, textTarget, addedTextOffsetY, fontName )
	if delay ~= nil and delay > 0 then
		wait(delay)
	end

	local matchTextTarget = endScreenTextConstant
	if textTarget ~= nil then
		matchTextTarget = textTarget
	end

	DemoEndingTextProperties = { Id = matchTextTarget, Group = "Overlay",
		Text = matchText,
		Justification = "CENTER",
		ShadowBlur = 0, ShadowColor = {0.141,0.125,0.231,1.0}, ShadowOffset = {0, 2},
		OutlineThickness = 1.0,
		OutlineColor = {0.141,0.125,0.231,1.0},
		Color = {0.902, 0.816, 1.00},
		Font = fontName,
		FontSize = 36,
		CharFadeTime = 0.16, CharFadeInterval = 0.05,
	}

	if addedTextOffsetY ~= nil then
		DemoEndingTextProperties.OffsetY = addedTextOffsetY
	end

	DisplayWorldText( DemoEndingTextProperties )

	if not playedMiscSoundRecently then
		PlaySound({ Name = "/SFX/Menu Sounds/UIBurn" })
		thread( PlayedMiscSoundRecently )
	end

end

OnKeyPressed{ "Alt D", Name = "Meteors",
	function(triggerArgs)

		--PanCamera({ Id = ballId, Duration = 1, OffsetY = 50, Delay = 0.2, EaseIn = 0.1, EaseOut = 0.1 })
		--FocusCamera({ Fraction = 0.425, Duration = 1, Delay = 0.2, ZoomType = "Ease" })

		local allObjects = GetIdsByType({ Names = {"StarPulse", "HighriseGlow01", "Torch"} })
		local nearbyObjects = { }

		for k, v in pairs( allObjects ) do
			if GetDistance({ Id = 40001, DestinationId = v }) < 1000 then
				table.insert( nearbyObjects, v )
			end
		end

		--DisplayInfoPanelText({ Name = tostring(#NearestLightedObjects) })

		SetOpacity({ Ids = nearbyObjects, Fraction = 0, Duration = 0.3, Angle = 1, MinInterval = 0.02, MaxInterval = 0.03 })
		wait(2)
		SetOpacity({ Ids = nearbyObjects, Fraction = 1, Duration = 0.3, Angle = 180, MinInterval = 0.02, MaxInterval = 0.03 })
	end
}

function SetupMatchSiteLightUpSequence()
	--NearestLightedObjects = GetIdsByType({ Names = {"StarPulse", "HighriseGlow01", "Torch"} })
	--SetOpacity({ Ids = NearestLightedObjects, Fraction = 0, Duration = 0.3 })

	local allObjects = GetIdsByType({ Names = {"StarPulse", "HighriseGlow01", "Torch"} })
	nearbyObjects = { }

	for k, v in pairs( allObjects ) do
		if GetDistance({ Id = 40001, DestinationId = v }) < 1400 and GetGroupName({ Id = v }) ~= "Art_Foreground" then
			table.insert( nearbyObjects, v )
		end
	end

	for k, v in pairs( allObjects ) do
		if GetDistance({ Id = 40000, DestinationId = v }) < 1000 and GetGroupName({ Id = v }) ~= "Art_Foreground" then
			table.insert( nearbyObjects, v )
		end
	end

	SetOpacity({ Ids = nearbyObjects, Fraction = 0, Duration = 0.3, Angle = 1, MinInterval = 0.02, MaxInterval = 0.03 })
end

function LightUpMatchSite( lightUpDelay )
	wait(lightUpDelay)
	--local allObjects = GetIdsByType({ Names = {"StarPulse", "HighriseGlow01", "Torch"} })
	--local nearbyObjects = { }

	SetOpacity({ Ids = nearbyObjects, Fraction = 1, Duration = 0.3, Angle = 1, MinInterval = 0.02, MaxInterval = 0.03 })
	SetOpacity({ Ids = nearbyObjects, Fraction = 1, Duration = 0.3, Angle = 180, MinInterval = 0.02, MaxInterval = 0.03 })
end

local EntranceTypeToArchetype =
{
	["Rukey"] = "PlayerSmall",
	["Hedwyn"] = "PlayerMedium",
	["Jodi"] = "PlayerLarge",
	["Mae"] = "PlayerMediumAlt",
	["Tizo"] = "PlayerImp",
	["Gilman"] = "PlayerTrail",
	["Pamitha"] = "PlayerFlying",
	["Bertrude"] = "PlayerMonster",
	["Volfred"] = "PlayerTree",
}

function SpecialMatchIntroEntrances( args, objectId, entranceDelay, cameraArgs, vfxArgs )
	MovingHoldCamera({ Enabled = false })
	if entranceDelay ~= nil and entranceDelay > 0 then
		wait(entranceDelay)
	end

	DebugPrint({ Text = tostring(args) })
	local character = GetCharacterTableByObjectId(objectId)
	local draftFXIgnoreOverheadoffset = true
	fxScale = 1.0
	if character ~= nil then
		fxScale = DraftFXScale[character.Archetype]
	else
		local archetype = EntranceTypeToArchetype[args]
		fxScale = DraftFXScale.archetype
	end
	if args == "Jodi" or args == "Rukey" or args == "Hedwyn" then
		draftFXIgnoreOverheadoffset = false
	end
	if vfxArgs == "UseDraftVFX" then
		PlayOverheadAnimation({ Name = "DraftChoose", DestinationId = objectId, IgnoreOverheadOffset = draftFXIgnoreOverheadoffset, Scale = fxScale })
	end

	local splashyEntrance = false
	if GetMapName({ }) == "MatchSiteC" then
		splashyEntrance = true
	end

	local currentZoom = GetCameraZoom({ })
	if args == "Rukey" then
		if cameraArgs == nil then
			PanCamera({ Id = objectId, Duration = 0.4, EaseIn = 0.05, EaseOut = 0.2 })
			FocusCamera({ ZoomType = "Overshoot", Relative = false, Duration = 0.3, Fraction = currentZoom * 1.65 })
		end

		PlayAnimation({ Name = "PlayerSmallEntranceLanding", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.1 })
		ShakeScreen({ Speed = 100, Distance = 2, Duration = 0.2, Delay = 0.2 })
		Rumble({ Fraction = 0.15, Duration = 0.2, Delay = 0.2 })

		if splashyEntrance then
			PlayOverheadAnimation({ Name = "BallSplashSmall", DestinationId = objectId, Delay = 0.2, IgnoreOverheadOffset = true, OffsetY = 20, OffsetX = -10, Group = "Shadows", Scale = 0.6 })
			PlaySound({ Name = "/SFX/Player Sounds/CurLandWet", Id = objectId, Delay = 0.2 })
		end

		--local currentZoom = GetCameraZoom({ })
		--PanCamera({ Id = TeamAObjectIds[1], Duration = 6, EaseIn = 1, EaseOut = 2 })
		--FocusCamera({ ZoomType = "Overshoot", Relative = false, Duration = 0.3, Fraction = currentZoom * 1.3 })
		--FocusCamera({ ZoomType = "Undershoot", Relative = true, Duration = 0.3, Fraction = 1.3 })
		--FocusCamera({ ZoomType = "Ease", Relative = false, Duration = 0.3, Fraction = 0.5 })
	elseif args == "Hedwyn" then

		local nextZoom = GetCameraZoom({ })
		if cameraArgs == nil then
			PanCamera({ Id = objectId, OffsetY = -150, Duration = 1.1, EaseIn = 0.4, EaseOut = 0.25	 })
			--FocusCamera({ ZoomType = "Overshoot", Relative = false, Duration = 3, Fraction = nextZoom * 2.15, Delay = 0 })
		end

		PlayAnimation({ Name = "PlayerMediumEntranceLanding", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.1 })
		ShakeScreen({ Speed = 400, Distance = 6, FalloffSpeed = 2000, Duration = 0.225, Delay = 0.17 })
		Rumble({ Fraction = 0.2, Duration = 0.25, Delay = 0.17 })

		if splashyEntrance then
			PlayOverheadAnimation({ Name = "BallSplashMedium", DestinationId = objectId, Delay = 0.17, IgnoreOverheadOffset = true, OffsetY = 10, OffsetX = -10, Group = "Shadows", Scale = 0.8 })
			PlaySound({ Name = "/SFX/Player Sounds/NomadLandWet", Id = objectId, Delay = 0.17 })
		end

		PlayAnimation({ Name = "PlayerMediumFidget", DestinationId = objectId, Delay = 1.3 })

	elseif args == "Jodi" then
		if cameraArgs == nil then
			PanCamera({ Id = objectId, OffsetY = -250, Duration = 0.4, EaseIn = 0.1, EaseOut = 0.2, Delay = 0.1 })
			FocusCamera({ ZoomType = "Overshoot", Relative = false, Duration = 0.23, Fraction = currentZoom * 0.8 })
		end

		PlayAnimation({ Name = "PlayerLargeEntranceLanding", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.1 })
		ShakeScreen({ Speed = 600, Distance = 14, Duration = 0.3, FalloffSpeed = 2000, Delay = 0.17 })
		Rumble({ Fraction = 0.35, Duration = 0.3, Delay = 0.17 })
		PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })

		if splashyEntrance then
			PlayOverheadAnimation({ Name = "JumpSplash", DestinationId = objectId, Delay = 0.17, IgnoreOverheadOffset = true, OffsetY = 50, OffsetX = -10, Group = "Shadows", Scale = 1.0 })
			PlayOverheadAnimation({ Name = "GroundImpactSplash", DestinationId = objectId, Delay = 0.17, IgnoreOverheadOffset = true, OffsetY = 50,
					OffsetX = -10, Group = "Shadows", Scale = 1.0 })
			PlaySound({ Name="/SFX/Player Sounds/DemonLandWet", Delay = 0.17 })
		else
			PlaySound({ Name="/SFX/Player Sounds/DemonLand", Delay = 0.17 })
		end

		if introMatch then
			--PlayAnimation({ Name = "PlayerLargeFidget", DestinationId = objectId, Delay = 1.3 })
		end

	elseif args == "Tizo" then
		PlayAnimation({ Name = "PlayerImpEntrance", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.225 })
		--ShakeScreen({ Speed = 300, Distance = 6, Duration = 0.3, FalloffSpeed = 2000, Delay = 0.35 })
		--Rumble({ Fraction = 0.25, Duration = 0.25, Delay = 0.35 })
		PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })



	elseif args == "Volfred" then
		PlayAnimation({ Name = "PlayerTreeWormholeEnd", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.2 })
		ShakeScreen({ Speed = 200, Distance = 4, Duration = 0.3, FalloffSpeed = 2000, Delay = 0.05 })
		Rumble({ Fraction = 0.25, Duration = 0.25, Delay = 0.05 })
		PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })


	elseif args == "Pamitha" then
		PlayAnimation({ Name = "PlayerFlyingAuraTurnOnTauntEscapeEnd", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.25, Delay = 0.3 })
		--ShakeScreen({ Speed = 300, Distance = 6, Duration = 0.3, FalloffSpeed = 2000, Delay = 0.35 })
		--Rumble({ Fraction = 0.25, Duration = 0.25, Delay = 0.35 })
		PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh", Delay = 0.27 })

	elseif args == "Bertrude" then
		PlayAnimation({ Name = "PlayerMonsterJumpEntrance", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.275 })
		ShakeScreen({ Speed = 400, Distance = 15, Duration = 0.325, FalloffSpeed = 1800, Delay = 0.28 })
		Rumble({ Fraction = 0.45, Duration = 0.3, Delay = 0.28 })
		PlaySound({ Name="/SFX/Player Sounds/DemonLand", Delay = 0.28 })
		--PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })

	elseif args == "Gilman" then
		SetScale({ Id = objectId, Fraction = 0.6 })
		PlayAnimation({ Name = "PlayerTrailIdle", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.285 })
		ShakeScreen({ Speed = 80, Distance = 2, Duration = 0.2, FalloffSpeed = 2000, Delay = 0.45 })
		--Rumble({ Fraction = 0.25, Duration = 0.25, Delay = 0.35 })
		--PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })

	elseif args == "Mae" then
		SetScale({ Id = objectId, Fraction = 0.8 })
		PlayAnimation({ Name = "PlayerMediumAltEvadeLanding", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.3, Delay = 0.1 })
		ShakeScreen({ Speed = 100, Distance = 2, Duration = 0.3, FalloffSpeed = 2000, Delay = 0.32 })
		--Rumble({ Fraction = 0.25, Duration = 0.25, Delay = 0.35 })
		--PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })

	end

	wait(1)

end

function SpecialMatchIntroEntrances2( args, objectId, entranceDelay )
	MovingHoldCamera({ Enabled = false })
	if entranceDelay ~= nil and entranceDelay > 0 then
		wait(entranceDelay)
	end

	if StartingPositions == nil then
		return
	end

	SetUnitProperty({ Name = "CollideWithObstacles", DestinationId = objectId, Value = false })
	SetUnitProperty({ Name = "CollideWithObstacles", DestinationId = objectId, Value = true, Delay = 10 })

	SetUnitProperty({ Name = "CollideWithUnits", DestinationId = objectId, Value = false })
	SetUnitProperty({ Name = "CollideWithUnits", DestinationId = objectId, Value = true, Delay = 10 })

	local currentZoom = GetCameraZoom({ })
	if args == "Rukey" then

		--ApplyUpgrade({ Name = "PlayerSmall", DestinationId = objectId })
		--SetUnitProperty({ Name = "ContinuousWeapon", Value = "null", DestinationId = objectId })

		--PlayAnimation({ Name = "PlayerSmallEntranceLanding", DestinationId = objectId })

		SetUnitProperty({ DestinationId = objectId, Name = "MoveGraphic", Value = "PlayerSmallWalk" })
		SetUnitProperty({ DestinationId = objectId, Name = "StopGraphic", Value = "PlayerSmallStop" })
		SetUnitProperty({ DestinationId = objectId, Name = "IdleGraphic", Value =  "PlayerSmallIdleNoHowl" })
		SetUnitProperty({ DestinationId = objectId, Name = "Speed", Value = 400 })

		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.1 })

		Teleport({ Id = objectId, DestinationId = StartingPositions[objectId], OffsetX = -1200 })
		Move({ Id = objectId, DestinationId = StartingPositions[objectId], Delay = 0.6 })

	elseif args == "Hedwyn" then

		--ApplyUpgrade({ Name = "PlayerMedium", DestinationId = objectId })
		--SetUnitProperty({ Name = "ContinuousWeapon", Value = "null", DestinationId = objectId })

		--PlayAnimation({ Name = "PlayerMediumEntranceLanding", DestinationId = objectId })

		SetUnitProperty({ DestinationId = objectId, Name = "MoveGraphic", Value = "PlayerMediumWalk" })
		SetUnitProperty({ DestinationId = objectId, Name = "StopGraphic", Value = "PlayerMediumStop" })
		SetUnitProperty({ DestinationId = objectId, Name = "IdleGraphic", Value =  "PlayerMediumIdle" })
		SetUnitProperty({ DestinationId = objectId, Name = "Speed", Value = 300 })

		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.1 })

		Teleport({ Id = objectId, DestinationId = StartingPositions[objectId], OffsetX = -1400 })
		Move({ Id = objectId, DestinationId = StartingPositions[objectId], Delay = 0.6 })
		--PlayAnimation({ Name = "PlayerMediumRun", DestinationId = objectId, Delay = 0.9 })

	elseif args == "Lendel" then

		TeamB.TeamBench[1].ObjectId = objectId
		SetScale({ Id = objectId, Fraction = TeamB.TeamBench[1].MatchScale })

		SetUnitProperty({ DestinationId = objectId, Name = "MoveGraphic", Value = "PlayerMediumWalk" })
		SetUnitProperty({ DestinationId = objectId, Name = "StopGraphic", Value = "PlayerMediumStop" })
		SetUnitProperty({ DestinationId = objectId, Name = "IdleGraphic", Value =  "PlayerMediumIdle" })
		SetUnitProperty({ DestinationId = objectId, Name = "Speed", Value = 300 })

		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.1 })

		Teleport({ Id = objectId, DestinationId = StartingPositions[objectId], OffsetX = 2000 })
		Move({ Id = objectId, DestinationId = StartingPositions[objectId], Delay = 0.6 })

	elseif args == "Accusers" then

		if objectId == 190005 then
			TeamB.TeamBench[2].ObjectId = objectId
			SetScale({ Id = objectId, Fraction = TeamB.TeamBench[2].MatchScale })
		elseif objectId == 190006 then
			TeamB.TeamBench[3].ObjectId = objectId
			SetScale({ Id = objectId, Fraction = TeamB.TeamBench[3].MatchScale })
		end

		--ApplyUpgrade({ Name = "PlayerMedium", DestinationId = objectId })
		--SetUnitProperty({ Name = "ContinuousWeapon", Value = "null", DestinationId = objectId })

		--PlayAnimation({ Name = "PlayerMediumEntranceLanding", DestinationId = objectId })
		SetUnitProperty({ DestinationId = objectId, Name = "MoveGraphic", Value = "PlayerMediumRun" })
		SetUnitProperty({ DestinationId = objectId, Name = "StopGraphic", Value = "PlayerMediumStop" })
		SetUnitProperty({ DestinationId = objectId, Name = "IdleGraphic", Value =  "PlayerMediumIdle" })
		SetUnitProperty({ DestinationId = objectId, Name = "Speed", Value = 300 })

		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.1 })


		SetUnitProperty({ Name = "Speed", DestinationId = objectId, Value = 500 })

		Teleport({ Id = objectId, DestinationId = StartingPositions[objectId], OffsetX = 2000 })
		Move({ Id = objectId, DestinationId = StartingPositions[objectId], Delay = 0.6 })
		--PlayAnimation({ Name = "PlayerMediumRun", DestinationId = objectId, Delay = 0.6 })
		--StopAnimation({ Name = "PlayerMediumRun", DestinationId = objectId, Delay = 2, PreventChain = true })

	elseif args == "Jodi" then

		--ApplyUpgrade({ Name = "PlayerLarge", DestinationId = objectId })
		--SetUnitProperty({ Name = "ContinuousWeapon", Value = "null", DestinationId = objectId })

		--PlayAnimation({ Name = "PlayerLargeEntranceLanding", DestinationId = objectId })
		SetUnitProperty({ DestinationId = objectId, Name = "MoveGraphic", Value = "PlayerLargeWalk" })
		SetUnitProperty({ DestinationId = objectId, Name = "StopGraphic", Value = "PlayerLargeStop" })
		SetUnitProperty({ DestinationId = objectId, Name = "IdleGraphic", Value =  "PlayerLargeIdle" })
		SetUnitProperty({ DestinationId = objectId, Name = "Speed", Value = 250 })

		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.1 })

		-- PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })
		-- PlaySound({ Name="/SFX/Player Sounds/DemonLand", Delay = 0.17 })

		Teleport({ Id = objectId, DestinationId = StartingPositions[objectId], OffsetX = -1200 })
		Move({ Id = objectId, DestinationId = StartingPositions[objectId], Delay = 0.6 })

	end

	wait(1)

	--MovingHoldCamera({ Enabled = true, Range = 1300 })
	--MovingHoldCamera({ Enabled = true, Range = {1000,1000} })

end

function TurnOnAurasWithFlair( extraDelay )
	if extraDelay ~= nil and extraDelay > 0 then
		wait(extraDelay)
	end
	wait(0.1)
	--preAuraSequenceZoom = GetCameraZoom({ })

	-- Specific custom order
	TurnOnUnitAuraWithFlair( PositionIds.LeftBottomId )
	TurnOnUnitAuraWithFlair( PositionIds.LeftMiddleId )
	TurnOnUnitAuraWithFlair( PositionIds.LeftTopId )
	wait(0.1)
	if CheckUnitAlive( PositionIds.RightBottomId ) then
		TurnOnUnitAuraWithFlair( PositionIds.RightBottomId )
	end
	if CheckUnitAlive( PositionIds.RightMiddleId ) then
		TurnOnUnitAuraWithFlair( PositionIds.RightMiddleId )
	end
	if CheckUnitAlive( PositionIds.RightTopId ) then
		TurnOnUnitAuraWithFlair( PositionIds.RightTopId )
	end
	--FocusCamera({ Fraction = preAuraSequenceZoom, ZoomType = "Ease", Duration = 1 })
	wait(1.6)
end

function TurnOnUnitAurasInChallenge( extraDelay )
	if extraDelay ~= nil and extraDelay > 0 then
		wait(extraDelay)
	end
	wait(0.1)
	TurnOnUnitAuraWithoutFlair( PositionIds.LeftMiddleId )
	if CheckUnitAlive( PositionIds.RightBottomId ) then
		TurnOnUnitAuraWithoutFlair( PositionIds.RightBottomId )
	end
	if CheckUnitAlive( PositionIds.RightMiddleId ) then
		TurnOnUnitAuraWithoutFlair( PositionIds.RightMiddleId )
	end
	if CheckUnitAlive( PositionIds.RightTopId ) then
		TurnOnUnitAuraWithoutFlair( PositionIds.RightTopId )
	end
	wait(0.5)
end

function TurnOnUnitAuraWithoutFlair( unitId, presentationType )
	local skipSideBPresentation = false
	if TeamB.ObjectIdsLookup[unitId] and not IsMultiplayerMatch() and presentationType == "Drafting" then
		skipSideBPresentation = true
	end
	local equippedAura = GetUnitDataStringValue({ Id = unitId, Property = "ContinuousWeapon" })
	SetWeaponProperty({ DestinationId = unitId, WeaponName = equippedAura, PropertyName = "Enabled", Value = true  })
	local soundDelay = GetWeaponDataValue({ WeaponName = equippedAura, Property = "ChargeTime", Id = unitId })
	if not skipSideBPresentation then
		PlaySound({ Name = "/SFX/Match SFX/AuraOnLoud", Id = unitId, Delay = soundDelay + 0.15 })
	end
end

function TurnOnUnitAuraWithFlair( unitId, presentationType )

	local skipSideBPresentation = false
	if TeamB ~= nil and TeamB.ObjectIdsLookup[unitId] and not IsMultiplayerMatch() and presentationType == "Drafting" then
		skipSideBPresentation = true
	end

	local unitData = CharacterCache[unitId]

	if unitData.Archetype == "PlayerLarge" and not skipSideBPresentation then
		thread( SpecialDemonTaunt, unitId, "overrideTaunt" )
	end

	if introMatch and not skipSideBPresentation then
		thread( EmoteEvade, unitData.VoicePrefix, unitId )
	end
	PlaySound({ Name = "/SFX/Player Sounds/PlayerJump", Id = unitId })
	local archetypeData = ArchetypeData[unitData.Archetype]
	if archetypeData ~= nil and not skipSideBPresentation then
		if unitData.SpecialAuraTurnOn then
			PlayAnimation({ Name = unitData.SpecialAuraTurnOn, DestinationId = unitId })
		else
			PlayAnimation({ Name = archetypeData.AuraOnAnimation, DestinationId = unitId })
		end
	end
	wait(0.25)
	local equippedAura = GetUnitDataStringValue({ Id = unitId, Property = "ContinuousWeapon" })
	SetWeaponProperty({ DestinationId = unitId, WeaponName = equippedAura, PropertyName = "Enabled", Value = true  })
	local soundDelay = GetWeaponDataValue({ WeaponName = equippedAura, Property = "ChargeTime", Id = unitId })
	if not skipSideBPresentation then
		PlaySound({ Name = "/SFX/Match SFX/AuraOnLoud", Id = unitId, Delay = soundDelay + 0.15 })
	end
end

function SpecialDemonTaunt( unitId, args )
	if TauntCooldowns[unitId] and args ~= "overrideTaunt" then
		return
	end

	local demonData = CharacterCache[unitId]

	if demonData.StartingTrait == "Nightwing01" then
		--DebugPrint({ Text = "Oralech Taunt" })
		thread( EmoteTauntingLargeOralech, demonData.VoicePrefix, unitId )
		wait(0.345)
		ShakeScreen({ Distance = 20, Speed = 400, Duration = 0.17, FalloffSpeed = 2000, Angle = 0 })
		Rumble({ Fraction = 0.17, Duration = 0.2, PlayerIndex = GetPlayerIndexByObjectId(unitId) })
		wait(0.410)
		ShakeScreen({ Distance = 20, Speed = 400, Duration = 0.17, FalloffSpeed = 2000, Angle = 180 })
		Rumble({ Fraction = 0.17, Duration = 0.2, PlayerIndex = GetPlayerIndexByObjectId(unitId) })
		wait(0.690)
		if CheckUnitAlive( unitId ) then
			ShakeScreen({ Distance = 40, Speed = 400, Duration = 0.4, FalloffSpeed = 3000 })
			Rumble({ Fraction = 0.6, Duration = 0.42, PlayerIndex = GetPlayerIndexByObjectId(unitId) })
		end
		wait(0.62)
		if CheckUnitAlive( unitId ) then
			ShakeScreen({ Distance = 3, Speed = 900, Duration = 0.8, FalloffSpeed = 2000 })
			Rumble({ Fraction = 0.4, Duration = 0.8, PlayerIndex = GetPlayerIndexByObjectId(unitId) })
		end
		return
	end

	if introMatch then
		thread( EmoteTauntingLarge, demonData.VoicePrefix, unitId )
	end

	if IsMultiplayerMatch() and matchOpeningSequence then
		return
	end

	wait(0.7)
	if CheckUnitAlive( unitId ) then
		ShakeScreen({ Distance = 20, Speed = 400, Duration = 0.4, FalloffSpeed = 2000, Angle = 90 })
		Rumble({ Fraction = 0.3, Duration = 0.4, PlayerIndex = GetPlayerIndexByObjectId(unitId) })
	end
	wait(1.1)
	if CheckUnitAlive( unitId ) then
		ShakeScreen({ Distance = 15, Speed = 300, Duration = 0.4, FalloffSpeed = 2000 })
		Rumble({ Fraction = 0.3, Duration = 0.4, PlayerIndex = GetPlayerIndexByObjectId(unitId) })
	end
end

OnControlPressed{ "Taunt",
	function(triggerArgs)
		PlayerTaunt( triggerArgs.PlayerIndex )
	end
}

TauntCooldowns = {}

local TauntParameters =
{
	["PlayerSmall"] = { MaxTauntVelocity = 700, TauntCooldown = 1.3 },
	["PlayerMedium"] = { MaxTauntVelocity = 500, TauntCooldown = 2.5, TauntRumble = 0.2, TauntRumbleDuration = 0.2, TauntRumbleDelay = 2.2 },
	["PlayerLarge"] = { MaxTauntVelocity = 260, VelocityHalt = true, TauntCooldown = 3.5 },
	["PlayerMediumAlt"] = { MaxTauntVelocity = 500, TauntCooldown = 2.0 },
	["PlayerImp"] = { MaxTauntVelocity = 1, TauntCooldown = 2.0 },
	["PlayerTrail"] = { MaxTauntVelocity = 1, TauntCooldown = 2.0 },
	["PlayerFlying"] = { MaxTauntVelocity = 1, TauntCooldown = 2.0 },
	["PlayerMonster"] = { MaxTauntVelocity = 300, TauntCooldown = 2.0 },
	["PlayerTree"] = { MaxTauntVelocity = 1, TauntCooldown = 2.0 },
}

function PlayerTaunt( tauntingPlayer, skipInputRule )

	if not skipInputRule then

		if introMatch then
			return
		end

		if IsControlDown({ Names = { "Sprint", "Jump", "Cast" }, PlayerIndex = tauntingPlayer }) then
			return
		end

		if not IsInputAllowed({ PlayerIndex = tauntingPlayer }) then
			return
		end
	end

	local currentTauntingUnit = GetActiveUnitId({ PlayerIndex = tauntingPlayer })
	local currentTauntingUnitData = GetCharacterTableByObjectId( currentTauntingUnit )
	local currentTauntingUnitParameters = TauntParameters[currentTauntingUnitData.Archetype]
	local tauntingUnitTeam = GetTeamByCharacterId( currentTauntingUnit )

	if currentTauntingUnitData == nil then
		return
	end

	if not CheckUnitAlive( currentTauntingUnit ) then
		return
	end

	if ImpifiedUnits[currentTauntingUnit] and not UnitHasSkill(currentTauntingUnitData, "TauntFeralImp") then
		return
	end

	local velocity = GetVelocity({ Id = currentTauntingUnit })
	local maxVelocity = currentTauntingUnitParameters.MaxTauntVelocity or 999999
	if velocity > maxVelocity then
		return
	end

	if GetZLocation({ Id = currentTauntingUnit }) > 1 then
		return
	end

	if ThrowingUnits[currentTauntingUnit] then
		return
	end

	if TauntCooldowns[currentTauntingUnit] then
		return
	end
	TauntCooldowns[currentTauntingUnit] = true

	if IsMoving({ Id = currentTauntingUnit }) then
		Stop({ Id = currentTauntingUnit })
	end

	if currentTauntingUnitParameters.VelocityHalt then
		Halt({ Id = currentTauntingUnit })
	end

	tauntingUnitTeam.LastTauntTime = _worldTime

	-- SFX
	if currentTauntingUnitData.Archetype == "PlayerSmall" then
		-- do nothing
	elseif currentTauntingUnitData.Archetype == "PlayerMedium" or currentTauntingUnitData.Archetype == "PlayerMediumAlt" then
		thread( EmoteTauntingMedium, currentTauntingUnitData.VoicePrefix, currentTauntingUnitData, tauntingUnitTeam )
	elseif currentTauntingUnitData.Archetype == "PlayerLarge" and currentTauntingUnitData.StartingTrait ~= "Nightwing01" then
		thread( EmoteTauntingLarge, currentTauntingUnitData.VoicePrefix, currentTauntingUnit )
	else
		thread( EmoteTaunting, currentTauntingUnitData.VoicePrefix, currentTauntingUnit )
	end

	-- taunt escape skill
	if TauntEscapees[currentTauntingUnit] then
		return
	end
	if UnitHasSkill(currentTauntingUnitData, "TauntEscape") then
		thread(CheckTauntEscape, currentTauntingUnit)
	end

	if not TauntEscapees[currentTauntingUnit] then
		if UnitHasSkill(currentTauntingUnitData, "TauntAreaFumble") then
			PlayAnimation({ Name = ArchetypeData[currentTauntingUnitData.Archetype].TauntAlternateAnimation, DestinationId = currentTauntingUnit })
			thread( SkillProcFeedback, currentTauntingUnit, "TauntAreaFumble", nil, nil, 0.3 )
		elseif currentTauntingUnitData.SpecialTauntAnimation then
			PlayAnimation({ Name = currentTauntingUnitData.SpecialTauntAnimation, DestinationId = currentTauntingUnit })
			--thread(GiveOnUnitFeedback, currentTauntingUnit, "MatchMessage_Taunt", nil, nil, 0.3 )
		else
			PlayAnimation({ Name = ArchetypeData[currentTauntingUnitData.Archetype].TauntAnimation, DestinationId = currentTauntingUnit })
			--thread(GiveOnUnitFeedback, currentTauntingUnit, "MatchMessage_Taunt", nil, nil, 0.3 )
		end
	end

	if UnitHasSkill(currentTauntingUnitData, "TauntTimeout") then
		Rumble({ Fraction = currentTauntingUnitParameters.TauntRumble, Duration = currentTauntingUnitParameters.TauntRumbleDuration, Delay = currentTauntingUnitParameters.TauntRumbleDelay })
		CheckTauntStun( currentTauntingUnit )
		TauntCooldowns[currentTauntingUnit] = false
	end

	if UnitHasSkill(currentTauntingUnitData, "TauntFeralImp") then
		Rumble({ Fraction = currentTauntingUnitParameters.TauntRumble, Duration = currentTauntingUnitParameters.TauntRumbleDuration, Delay = currentTauntingUnitParameters.TauntRumbleDelay })
		CheckTauntFeralImp( currentTauntingUnit )
		TauntCooldowns[currentTauntingUnit] = false
		return
	end

	if UnitHasSkill(currentTauntingUnitData, "TauntSwap") then
		Rumble({ Fraction = currentTauntingUnitParameters.TauntRumble, Duration = currentTauntingUnitParameters.TauntRumbleDuration, Delay = currentTauntingUnitParameters.TauntRumbleDelay })
		CheckTauntSwap( currentTauntingUnit )
		TauntCooldowns[currentTauntingUnit] = false
		return
	end

	if UnitHasSkill(currentTauntingUnitData, "TauntBallTeleport") then
		Rumble({ Fraction = currentTauntingUnitParameters.TauntRumble, Duration = currentTauntingUnitParameters.TauntRumbleDuration, Delay = currentTauntingUnitParameters.TauntRumbleDelay })
		if CheckTauntBallTeleport( currentTauntingUnit ) then
			TauntCooldowns[currentTauntingUnit] = false
			return
		end
	end

	if UnitHasSkill(currentTauntingUnitData, "TauntSaplingExplode" ) then
		CheckTauntSaplingExplode( currentTauntingUnitData )
	end

	if currentTauntingUnitData.Archetype == "PlayerLarge" then
		thread( SpecialDemonTaunt, currentTauntingUnit, "overrideTaunt" )
	end

	if currentTauntingUnitParameters.TauntRumble ~= nil then
		Rumble({ Fraction = currentTauntingUnitParameters.TauntRumble, Duration = currentTauntingUnitParameters.TauntRumbleDuration, Delay = currentTauntingUnitParameters.TauntRumbleDelay })
	end

	thread(CheckTauntSkills, currentTauntingUnit )
	wait(currentTauntingUnitParameters.TauntCooldown)
	TauntCooldowns[currentTauntingUnit] = false

end

tauntScorers = { }

function CheckTauntScore( taunter )
	wait(2.4)
	tauntScorers[taunter] = true
	PlayOverheadAnimation({ Name = "ScoreBuffFront", DestinationId = taunter, IgnoreOverheadOffset = true })
	PlayOverheadAnimation({ Name = "ScoreBuffBack", DestinationId = taunter, IgnoreOverheadOffset = true })
	thread(SkillProcFeedback, taunter, "TauntScoreSkill10", nil )
	wait(7)
	tauntScorers[taunter] = false
	StopAnimation({ Name = "ScoreBuffFront", DestinationId = taunter })
	StopAnimation({ Name = "ScoreBuffBack", DestinationId = taunter })
end

function CheckTauntStamina( taunter )
	thread(SkillProcFeedback, taunter, "TauntStaminaSKill" )
	PlayOverheadAnimation({ Name = "SpeedUpFront", DestinationId = taunter, IgnoreOverheadOffset = true })
	FireWeapon({ Name = "InvisibleStaminaRecover", DestinationId = taunter })
	wait(1)
	StopAnimation({ Name = "SpeedUpFront", DestinationId = taunter })
end

UsedPolymorph = {}
function CheckTauntPolymorphSide( taunter )
	local livingUnitIds = { }
	if Contains(TeamBObjectIds, taunter) then
		if UsedPolymorph[taunter] then
			return
		end
		UsedPolymorph[taunter] = true
		for k, id in pairs( TeamA.ObjectIds ) do
			if CheckUnitAlive( id ) then
				table.insert( livingUnitIds, id )
			end
		end
	else
		if UsedPolymorph[taunter] then
			return
		end
		UsedPolymorph[taunter] = true
		for k, id in pairs( TeamB.ObjectIds ) do
			if CheckUnitAlive( id ) then
				table.insert( livingUnitIds, id )
			end
		end
	end

	thread(SkillProcFeedback, taunter, "TauntPolymorphSide", nil )
	for k, v in pairs(livingUnitIds) do
		PlayOverheadAnimation({ Name = "PlayerMonsterJumpFx", DestinationId = v, IgnoreOverheadOffset = true })
		Impify(v)
		wait(0.3)
	end
end

function CheckTauntRandomKill( taunter )
	local livingUnitIds = { }
	for k, id in pairs( TeamA.ObjectIds ) do
		if CheckUnitAlive( id ) then
			table.insert( livingUnitIds, id )
		end
	end
	for k, id in pairs( TeamB.ObjectIds ) do
		if CheckUnitAlive( id ) then
			table.insert( livingUnitIds, id )
		end
	end
	for k, id in pairs( GetIdsByType({ Name = "DefenderUnit", RequireAlive = true }) ) do
		table.insert( livingUnitIds, id )
	end
	for k, id in pairs( GetIdsByType({ Name = "DefenderUnitMini", RequireAlive = true }) ) do
		table.insert( livingUnitIds, id )
	end

	local unluckyUnit = GetRandomValue(livingUnitIds)
	if unluckyUnit == nil then
		return
	end

	FireWeapon({ Name = "PoisonBoltSkyDamaging", DestinationId = unluckyUnit })
end

function CheckTauntBallTeleport( taunter )
	if BallCarrierId == 0 then
		PlayAnimation({ Name = "MediumAltTauntBallTeleport", DestinationId = taunter })
		PlayOverheadAnimation({ Name = "DraftSwitchFx", DestinationId = taunter, IgnoreOverheadOffset = true, Scale = 1.3, Delay = 0.525 })
		wait(0.525)
		thread(DoTeleportSlowCam, taunter )
		Teleport({ Id = taunter, DestinationId = ballId })
		thread( SkillProcFeedback, taunter, "TauntBallTeleport", nil )
		MatchCameraSelector( "Teleport" )
		return true
	end
	return false
end

TauntSwappedThisRound = { }

function ArchetypeColdEntrance( objectId )
	local unitData = GetCharacterTableByObjectId( objectId )
	if GetMapName({ }) == "MatchSiteC" then
		local splashyEntrance = true
	end

	if ImpifiedUnits[objectId] then
		return
	end

	if unitData.Archetype == "PlayerSmall" then
		PlayAnimation({ Name = "PlayerSmallEntranceLanding", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.1 })
		ShakeScreen({ Speed = 100, Distance = 2, Duration = 0.2, Delay = 0.2 })
		Rumble({ Fraction = 0.15, Duration = 0.2, Delay = 0.2 })

		if splashyEntrance then
			PlayOverheadAnimation({ Name = "BallSplashSmall", DestinationId = objectId, Delay = 0.2, IgnoreOverheadOffset = true, OffsetY = 20, OffsetX = -10, Group = "Shadows", Scale = 0.6 })
			PlaySound({ Name = "/SFX/Player Sounds/CurLandWet", Id = objectId, Delay = 0.2 })
		end

	elseif unitData.Archetype == "PlayerMedium" then
		PlayAnimation({ Name = "PlayerMediumEntranceLanding", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.1 })
		ShakeScreen({ Speed = 400, Distance = 6, FalloffSpeed = 2000, Duration = 0.225, Delay = 0.17 })
		Rumble({ Fraction = 0.2, Duration = 0.25, Delay = 0.17 })

		if splashyEntrance then
			PlayOverheadAnimation({ Name = "BallSplashMedium", DestinationId = objectId, Delay = 0.17, IgnoreOverheadOffset = true, OffsetY = 10, OffsetX = -10, Group = "Shadows", Scale = 0.8 })
			PlaySound({ Name = "/SFX/Player Sounds/NomadLandWet", Id = objectId, Delay = 0.17 })
		end

	elseif unitData.Archetype == "PlayerLarge" then

		PlayAnimation({ Name = "PlayerLargeEntranceLanding", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.1 })
		ShakeScreen({ Speed = 600, Distance = 14, Duration = 0.3, FalloffSpeed = 2000, Delay = 0.17 })
		Rumble({ Fraction = 0.35, Duration = 0.3, Delay = 0.17 })
		PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })

		if splashyEntrance then
			PlayOverheadAnimation({ Name = "JumpSplash", DestinationId = objectId, Delay = 0.17, IgnoreOverheadOffset = true, OffsetY = 50, OffsetX = -10, Group = "Shadows", Scale = 1.0 })
			PlayOverheadAnimation({ Name = "GroundImpactSplash", DestinationId = objectId, Delay = 0.17, IgnoreOverheadOffset = true, OffsetY = 50,
					OffsetX = -10, Group = "Shadows", Scale = 1.0 })
			PlaySound({ Name="/SFX/Player Sounds/DemonLandWet", Delay = 0.17 })
		else
			PlaySound({ Name="/SFX/Player Sounds/DemonLand", Delay = 0.17 })
		end

	elseif unitData.Archetype == "PlayerImp" then
		PlayAnimation({ Name = "PlayerImpEntrance", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.225 })
		--ShakeScreen({ Speed = 300, Distance = 6, Duration = 0.3, FalloffSpeed = 2000, Delay = 0.35 })
		--Rumble({ Fraction = 0.25, Duration = 0.25, Delay = 0.35 })
		PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })

	elseif unitData.Archetype == "PlayerTree" then
		PlayAnimation({ Name = "PlayerTreeWormholeEnd", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.2 })
		ShakeScreen({ Speed = 200, Distance = 4, Duration = 0.3, FalloffSpeed = 2000, Delay = 0.05 })
		Rumble({ Fraction = 0.25, Duration = 0.25, Delay = 0.05 })
		PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })

	elseif unitData.Archetype == "PlayerFlying" then
		PlayAnimation({ Name = "PlayerFlyingAuraTurnOnTauntEscapeEnd", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.25, Delay = 0.3 })
		--ShakeScreen({ Speed = 300, Distance = 6, Duration = 0.3, FalloffSpeed = 2000, Delay = 0.35 })
		--Rumble({ Fraction = 0.25, Duration = 0.25, Delay = 0.35 })
		PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh", Delay = 0.27 })

	elseif unitData.Archetype == "PlayerMonster"then
		PlayAnimation({ Name = "PlayerMonsterJumpEntrance", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.275 })
		ShakeScreen({ Speed = 400, Distance = 15, Duration = 0.325, FalloffSpeed = 1800, Delay = 0.28 })
		Rumble({ Fraction = 0.45, Duration = 0.3, Delay = 0.28 })
		PlaySound({ Name="/SFX/Player Sounds/DemonLand", Delay = 0.28 })
		--PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })

	elseif unitData.Archetype == "PlayerTrail" then
		PlayAnimation({ Name = "PlayerTrailJumpEntrance", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.285 })
		ShakeScreen({ Speed = 80, Distance = 2, Duration = 0.2, FalloffSpeed = 2000, Delay = 0.45 })
		--Rumble({ Fraction = 0.25, Duration = 0.25, Delay = 0.35 })
		--PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })

	elseif unitData.Archetype == "PlayerMediumAlt" then
		PlayAnimation({ Name = "PlayerMediumAltEvadeLanding", DestinationId = objectId })
		SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.3, Delay = 0.1 })
		ShakeScreen({ Speed = 100, Distance = 2, Duration = 0.3, FalloffSpeed = 2000, Delay = 0.32 })
		--Rumble({ Fraction = 0.25, Duration = 0.25, Delay = 0.35 })
		--PlaySound({ Name="/SFX/Match SFX/FastMoveWhoosh" })
	end
end

function CheckTauntSwap( taunter )
	if IsLastUnitStanding( taunter ) or TauntSwappedThisRound[taunter] then
		wait(1.0)
		return
	end

	local nearestTeammate = GetNearestLivingTeamMate( taunter )
	local swapA = SpawnObstacle({ Name = "InvisibleTarget", Group = "SwapHelpers", DestinationId = taunter })
	local swapB = SpawnObstacle({ Name = "InvisibleTarget", Group = "SwapHelpers", DestinationId = nearestTeammate })

	thread( DoTeleportSlowCam, taunter, nearestTeammate )
	Teleport({ Id = taunter, DestinationId = swapB })
	Teleport({ Id = nearestTeammate, DestinationId = swapA })
	PlayOverheadAnimation({ Name = "DraftSwitchFx", DestinationIds = {swapA, swapB}, IgnoreOverheadOffset = true })
	SwitchActiveUnit({ Id = nearestTeammate, PlayerIndex = GetPlayerIndexByObjectId(taunter) })
	thread(SkillProcFeedback, taunter, "TauntSwap", nil )

	ArchetypeColdEntrance( nearestTeammate )
	wait(2.0)
end

function CheckTauntSaplingExplode( characterData )
	local explodedSapling = nil
	if characterData.DeathDefenderUnitId then
		explodedSapling = characterData.DeathDefenderUnitId
	elseif characterData.DefenderUnitId then
		explodedSapling = characterData.DefenderUnitId
	end
	if explodedSapling == nil or not IsAlive({ Id = explodedSapling }) then
		return
	end
	thread(SkillProcFeedback, explodedSapling, "MatchMessage_TauntSaplingExplode", nil )
	PlaySound({ Name = "/SFX/Match SFX/SaplingSpawnIn", Id = explodedSapling })
	PlaySound({ Name = "/SFX/Match SFX/PositiveTalismanProc_1", Id = explodedSapling })
	SetColor({ Id = explodedSapling, Color = Color.Red, Duration = 0.5 })
	wait(0.5)
	FireWeaponFromUnit({ Weapon = "SaplingDetonate", Id = explodedSapling })
	SetColor({ Id = explodedSapling, Color = Color.White })
end

-- special handling for everyone being dead
OnControlPressed{ "Taunt",
	function(triggerArgs)
		local tauntingUnit = triggerArgs.triggeredById
		local team = GetTeamByCharacterId( tauntingUnit )
		if tauntingUnit == nil or team == nil or uncommonMatch then
			return
		end
		local tauntUnitData = GetCharacterTableByObjectId( tauntingUnit )
		if IsWholeTeamDeadOrRespawning( team ) and TeamHasSkillDrafted(team, "TauntSaplingExplode") then
			for k, v in pairs(team.TeamBench) do
				CheckTauntSaplingExplode( v )
			end
		end
	end
}

TauntEscapees = { }
function CheckTauntEscape( taunter )
	TauntEscapees[taunter] = true
	AdjustZLocation({ Id = taunter, Distance = 3000, Duration= 0.3, Delay = 0.2 })
	SetInvulnerable({ Id = taunter })
	SetUnitProperty({ Name = "LegalPassTarget", Value = false, DestinationId = taunter })
	thread( SkillProcFeedback, taunter, "TauntEscape" )
	PlayAnimation({ Name = "PlayerFlyingAuraTurnOnTauntEscapeStart", DestinationId = taunter, Delay = 0.0 })
	wait(3.0)
	PlayAnimation({ Name = "PlayerFlyingAuraTurnOnTauntEscapeEnd", DestinationId = taunter, Delay = 0.0 })
	AdjustZLocation({ Id = taunter, Distance = -3000, Duration = 0.4 })
	wait(0.5)
	SetUnitProperty({ Name = "LegalPassTarget", Value = true, DestinationId = taunter })
	SetVulnerable({ Id = taunter })
	TauntEscapees[taunter] = false
end

TauntStunnedThisRound = { }
function CheckTauntStun( taunter )
	if TauntStunnedThisRound[taunter] then
		wait(1.0)
		return
	end
	TauntStunnedThisRound[taunter] = true
	FireWeapon({ Name = "TauntStunWeapon", DestinationId = taunter })

	TauntStunFeedback( taunter )

end

function TauntStunFeedback( taunter )

	thread( SkillProcFeedback, taunter, "TauntTimeout" )
	wait(1)
	PlaySound({ Name = "/SFX/Match SFX/PositiveTalismanProc_2", Id = taunter })
	wait(1)
	PlaySound({ Name = "/SFX/Match SFX/PositiveTalismanProc_2", Id = taunter })
	wait(1)
	PlaySound({ Name = "/SFX/Match SFX/PositiveTalismanProc_2", Id = taunter })
	wait(1)
	thread(SkillProcFeedback, taunter, "MatchMessage_Resume")
	PlaySound({ Name = "/SFX/Match SFX/PositiveTalismanProc_1", Id = taunter })
end

function CheckTauntFeralImp( taunter )
	local objectId = taunter
	if not ImpifiedUnits[objectId] then
		Impify( objectId )
	elseif ImpifiedUnits[objectId] then
		Deimpify( objectId )
	end
end

function CheckTauntSkills( taunter )
	local characterInfo = GetCharacterTableByObjectId( taunter )
	if GetUnitSkillValue(characterInfo, "TauntScoreSkill") > 0 then
		thread(CheckTauntScore, taunter )
	end
	if UnitHasSkill(characterInfo, "TauntPolymorphSide") then
		thread(CheckTauntPolymorphSide, taunter )
	end
	--[[
	if UnitHasSkill(characterInfo, "TauntStaminaSKill") then
		thread(CheckTauntStamina, taunter )
	end
	if UnitHasSkill(characterInfo, "TauntRandomKillSkill") then
		thread(CheckTauntRandomKill, taunter )
	end
	]]

end

OnHit{ "_UNIT",
	RequiredWeapon = "TauntAreaFumble",
	function(triggerArgs)
		local unitData = GetCharacterTableByObjectId( triggerArgs.triggeredById )
		if unitData == nil then
			return
		end
		if BallCarrierId == triggerArgs.triggeredById then
			UnitFumble(triggerArgs.triggeredById)
		end
	end
}

BanishScorers = { }
function CheckBanishScoreBonus( unitId )
	if BanishScorers[unitId] then
		return
	end
	BanishScorers[unitId] = true
	PlayOverheadAnimation({ Name = "ScoreBuffFront", DestinationId = unitId, IgnoreOverheadOffset = true })
	PlayOverheadAnimation({ Name = "ScoreBuffBack", DestinationId = unitId, IgnoreOverheadOffset = true })
	thread(SkillProcFeedback, unitId, "BanishScoreBonus", nil, 10 )
	wait(10)
	BanishScorers[unitId] = false
	StopAnimation({ Name = "ScoreBuffFront", DestinationId = unitId })
	StopAnimation({ Name = "ScoreBuffBack", DestinationId = unitId })
end


function AddMeteorSplash( meteorDestination )
	wait(0.2)
	splash = SpawnObstacle({ Name = "BallDropSplash", DestinationId = meteorDestination, OffsetX = -5, OffsetY = -38, Group = "Shadows" })
	Delete({ Id = splash, Delay = 0.6 })
end

function MeteoriteSlam( meteorDestination, goalFireId, slamDelay, ballShockwave, colorGradeSetting, shortMeteor )

	if slamDelay ~= nil and slamDelay > 0 then
		wait(slamDelay)
	end

	local meteorOffsetX = -45
	if meteorDestination == goalB then
		meteorOffsetX = 130
	end

	local shockwave = nil
	if ballShockwave ~= nil then
		shockwave = SpawnObstacle({ Name = "BallDropShockwave", DestinationId = meteorDestination, OffsetX = -5, OffsetY = -28, Group = "Shadows" })
		AdjustColorGrading({ Name = "MeteorStrike", Duration = 0 })
		if GetMapName({ }) == "MatchSiteC" then
			thread( AddMeteorSplash, meteorDestination )
		end

		thread(ResumeDefaultMatchColorGrade, 2, 0.1 )
	end

	local meteorite = nil
	if ballShockwave == nil then
		meteorite = SpawnObstacle({ Name = "MeteoriteTest", DestinationId = meteorDestination, OffsetX = 0, OffsetY = 30, Group = "Art_Standing01" })
		if colorGradeSetting ~= nil then
			AdjustColorGrading({ Name = colorGradeSetting, Duration = 0 })
			thread(ResumeDefaultMatchColorGrade, 1.5, 0.1 )
		end
	end

	if meteorDestination == ballId or shortMeteor then
		PlaySound({ Name = "/SFX/Match SFX/MeteorStrikeShort", Id = meteorDestination })
	else
		PlaySound({ Name = "/SFX/Match SFX/MeteorStrike", Id = meteorDestination })
	end

	if goalFireId ~= nil then
		SetOpacity({ Id = goalFireId, Fraction = 1, Duration = 0.1 })
	end

	if shockwave == nil then
		ShakeScreen({ Speed = 800, Distance = 30, FalloffSpeed = 2000, Duration = 0.15, Delay = 0.025 })
		AllRumble({ Fraction = 0.6, Duration = 0.5, Delay = 0.075 })

		SetOpacity({ Id = meteorite, Duration = 0.2, Delay = 2.5, Fraction = 0 })
		Destroy({ Id = meteorite, Delay = 3 })
	else
		ShakeScreen({ Speed = 1000, Distance = 34, FalloffSpeed = 2000, Duration = 0.15, Delay = 0.025, Angle = 90 })
		AllRumble({ Fraction = 0.5, Duration = 0.45, Delay = 0.075 })

		SetOpacity({ Id = shockwave, Duration = 0.2, Delay = 2 })
		Destroy({ Id = shockwave, Delay = 2.2 })
	end

	if goalFireId == nil then
		return
	end

	wait(0.01)
	PlayAnimation({ Name = "BonfireIgnite", DestinationId = goalFireId })

	if goalFireId == TeamA.PyreId then
		ColorBonfire( TeamA )
		ColorMeteorite( TeamA, meteorite )
	else
		ColorBonfire( TeamB )
		ColorMeteorite( TeamB, meteorite )
	end

	wait(1.34)

	if goalFireId == TeamA.PyreId then
		ColorBonfire( TeamA )
		if goalAFireAmbientLoopId == nil then
			goalAFireAmbientLoopId = PlaySound({ Name = "/SFX/Match SFX/FireAmbientLoopMATCH", Id = goalFireId })
			SetSoundCueValue({ Name = "FireSizeA", Id = goalAFireAmbientLoopId, Value = 1.0 })
		end
	elseif goalFireId == TeamB.PyreId then
		ColorBonfire( TeamB )
		if goalBFireAmbientLoopId == nil then
			goalBFireAmbientLoopId = PlaySound({ Name = "/SFX/Match SFX/FireAmbientLoopMATCH_2", Id = goalFireId })
			SetSoundCueValue({ Name = "FireSizeB", Id = goalAFireAmbientLoopId, Value = 1.0 })
		end
	end

end

--[[ * RESPAWNING * ]]

function Purgatory( respawnedUnitId )

	local restoredSpeed = GetUnitDataValue({ Id = respawnedUnitId, Property = "Speed" })
	SetUnitProperty({ Name = "Speed", Value = 0, DestinationId = respawnedUnitId })
	PlayOverheadAnimation({ Name = "DebuffFront", DestinationId = respawnedUnitId, OffsetY = 100 })
	PlayOverheadAnimation({ Name = "DebuffBack", DestinationId = respawnedUnitId, OffsetY = 100 })

	wait(1)
	SetUnitProperty({ Name = "Speed", Value = restoredSpeed, DestinationId = respawnedUnitId })
	StopAnimation({ Name =  "DebuffFront", DestinationId = respawnedUnitId })
	StopAnimation({ Name ="DebuffBack", DestinationId = respawnedUnitId })

end

BallCarrierId = 0

numRapidKills = 0
numRecentTeamAKills = 0
numRecentTeamBKills = 0

-- for multi-kill VO
OnAnyLoad{
	function( triggerArgs )
		SetTimer({ Name = "KillVOTimer", Id = 0 })
	end
}

OnTimer{ "KillVOTimer >= 2",
	function( triggerArgs )
		StopTimer({ Name = "KillVOTimer" })
		SetTimer({ Name = "KillVOTimer", Id = 0 })
		numRapidKills = 0
		numRecentTeamAKills = 0
		numRecentTeamBKills = 0
	end
}

InfiniteRespawnTime = 999999

local emoteKillingWeapons = ToLookup({ "ResetAura", "ResetAuraSmall", "ResetAuraLarge", "ResetAuraImp", "ResetAuraTrail", "ResetAuraMonster", "CastTrail" })
local rumbleKillingWeapons = ToLookup({ "ResetAura", "ResetAuraSmall", "ResetAuraLarge", "ResetAuraImp", "ResetAuraTrail", "ResetAuraMonster", })

OnDestroyAny{ "TeamA TeamB", function( triggerArgs )

	if triggerArgs.triggeredById == nil then
		return
	end

	if ImpifiedUnits[triggerArgs.triggeredById] then
		FireWeapon({ Name = "ImpifyDeathWeapon", DestinationId = triggerArgs.triggeredById })
		Deimpify( triggerArgs.triggeredById, "DeathDispell" )
	end

	if deathPoolActive then
		CheckDeathPools( triggerArgs.triggeredById, triggerArgs.KillerId )
	end

	local deadCharTeam = GetMatchTeamByCharacterId( triggerArgs.triggeredById )
	local deadPlayerObjectId = triggerArgs.triggeredById
	local deadPlayer = CharacterCache[triggerArgs.triggeredById]
	local deathWeapon = triggerArgs.name
	local booker = triggerArgs.KillerId
	local killingPlayer = CharacterCache[booker]

	if deadPlayer == nil and killingPlayer == nil then
		return
	end

	local perfectKill = false
	if triggerArgs.IsPerfectCharge then
		perfectKill = true
	end

	if deadPlayer == nil then
		CheckKillSkills( killingPlayer, deathWeapon, nil, deadPlayerObjectId, perfectKill )
		return
	end
	deadPlayer.LastDeathTime = _worldTime

	-- tutorial nonsense
	if fakeJumpPresentation then
		DisableInput({ PlayerIndex = 1 })
		Teleport({ Id = deadPlayerObjectId, DestinationId = 40011 })
		AngleTowardTarget({ Id = deadPlayerObjectId, DestinationId = goalB })
		Revive({ Id = deadPlayerObjectId, Delay = 0.3 })
		SetOpacity({ Id = deadPlayerObjectId, Fraction = 1, Duration = 0.2, Delay = 0.3 })
		PlayOverheadAnimation({ Name = "DraftPickedGlow", DestinationId = deadPlayerObjectId })
		EnableInput({ PlayerIndex = 1, Delay = 0.3 })
		thread( PlayIntroMatchSpeech, "jumpfail" )
		if timesJumpFailed >= 5 then -- this is counted in AudioScripts
			thread(MakeJumpTutorialGoalEasier)
		end
		return
	end

	if deadPlayer ~= SacrificeScorer or deadPlayer.ImmuneToSacrifice then
		if fakeDeathTimer and TeamB.ObjectIdsLookup[deadPlayerObjectId] then
			deadPlayer.RespawnTimer = InfiniteRespawnTime
		else
			deadPlayer.RespawnTimer = deadPlayer.BaseRespawnTimer
		end
	end

	if deadCharTeam.PrevControlIdLiving == triggerArgs.triggeredById then
		deadCharTeam.PrevControlIdLiving = nil
	end

	CheckKillSkills( killingPlayer, deathWeapon, deadPlayer, deadPlayerObjectId, perfectKill )
	CheckDeathSkills( deadPlayer, deadCharTeam, booker, killingPlayer )
	CheckAllDeadRespawn( deadCharTeam, deadPlayer )

	if deadCharTeam.CurrentControlId == deadPlayerObjectId then
		if not matchAside then
			Rumble({ Fraction = 0.25, Duration = 0.3, PlayerIndex = deadCharTeam.PlayerIndex })
		end
	end

	-- Drop the ball if this was carrying it
	local preventFumble = false

	if BallCarrierId == triggerArgs.triggeredById then

		if CheckUnitSkillChance(deadPlayer, "PassOnDeath") then -- for items
			preventFumble = DeathPass( deadPlayer )
		end

		if not preventFumble then
			Unattach({ Id = ballId, CenterGeometry = true, AttachedToId = triggerArgs.triggeredById })
			BallCarrierId = 0
			PlayAnimation({ DestinationId = ballId, Name = "InkBallEmpty" })
		end
		throwHeld = false

		thread( MatchDeathAudio, deadPlayer )

		if not preventFumble then
			UpdateMatchMusic( "nopossession" )
		end

		-- Fumble the ball based on luck stats
		if booker ~= nil and not preventFumble then

			if fakeFumble then
				ApplyUpwardForce({ Id = ballId, Speed = 1200 })
				ApplyForce({ Id = ballId, Speed = 275, Angle = 45 })
				SetScale({ Id = ballId, Fraction = 1, Duration = 0.9 })
			elseif fakeFumble2 then
				ApplyUpwardForce({ Id = ballId, Speed = 500 })
				ApplyForce({ Id = ballId, Speed = 340, Angle = 300 })
				SetScale({ Id = ballId, Fraction = 1, Duration = 0.9 })
			elseif fakeFumble3 then
				ApplyUpwardForce({ Id = ballId, Speed = 2000 })
				ApplyForce({ Id = ballId, Speed = 500, Angle = 180 })
				SetScale({ Id = ballId, Fraction = 1, Duration = 0.9 })
			elseif killingPlayer ~= nil and UnitHasSkill( killingPlayer, "BallKillSteal" ) then
				Attach({ Id = ballId, DestinationId = booker })
				BallCarrierId = booker
				Halt({ Id = ballId })
				UpdateBallScale()
				thread( SkillProcFeedback, booker, "BallKillSteal" )
				thread( MatchQuipAudio )
			else

				local fumbleStat = 0
				if killingPlayer ~= nil then
					fumbleStat = killingPlayer.PlayerAttributeLuck
				else
					fumbleStat = 5
				end
				local fumbleUpwardVelocity = 0
				local fumbleSidewaysVelocity = 0
				if fumbleStat ~= nil then
					fumbleSidewaysVelocity = 800 + (fumbleStat * 60 )
					fumbleUpwardVelocity = 1200 - (fumbleStat * 10 )
				else
					fumbleSidewaysVelocity = 700
					fumbleUpwardVelocity = 1600
				end

				if crazyBallEvent then
					fumbleUpwardVelocity = fumbleUpwardVelocity * 0.45
					fumbleSidewaysVelocity = fumbleSidewaysVelocity * 1.25
				end

				ApplyUpwardForce({ Id = ballId, Speed = (fumbleUpwardVelocity / 1.8) })

				local killerAngle = GetAngle({ Id = booker })
				if killerAngle ~= nil then
					ApplyForce({ Id = ballId, Speed = (fumbleSidewaysVelocity / 1.8), Angle = killerAngle, UsingRadians = true })
				else
					local fumbleAngle = math.random(0, 360)
					ApplyForce({ Id = ballId, Speed = (fumbleSidewaysVelocity / 1.8), Angle = fumbleAngle })
				end
				SetScale({ Id = ballId, Fraction = 1, Duration = 0.9 })
			end
		end

	end

	cameraDirty = true

	-- Move non-critical items to another frame
	wait(0.02)

	-- emote VO
	if deadPlayer ~= nil then
		if RemoteTeam.ObjectIdsLookup[deadPlayerObjectId] or deadPlayer.LocalHumanControlled then
			if deathWeapon ~= "CastImp" then
				if not blockDeathEmote then
					thread( EmoteDying, deadPlayer.VoicePrefix, deadPlayerObjectId )
				end
			else
				thread( EmotePowerAttack, deadPlayer.VoicePrefix, deadPlayerObjectId )
			end
		end
		if jumpingTowardGoalId == deadPlayer.ObjectId then
			thread( InterceptionAudio )
			jumpingTowardGoalId = nil
		end
	end
	if killingPlayer ~= nil then
		if emoteKillingWeapons[deathWeapon] then
			thread( EmoteKilling, killingPlayer.VoicePrefix, booker )
		end

		if rumbleKillingWeapons[deathWeapon] then
			local killingCharTeam = GetMatchTeamByCharacterId( booker )
			if not matchAside and killingCharTeam.CurrentControlId == booker then
				Rumble({ Fraction = 0.17, Duration = 0.17, PlayerIndex = killingCharTeam.PlayerIndex })
			end
		end
	end

	-- first blood VO
	if not firstBloodVO and deathWeapon ~= "CastImp" then
		firstBloodVO = true
		thread( FirstBloodAudio )
	end

	-- character banishment VO
	if not IsEmpty( deadPlayer.BanishedSpeechLines ) then
		local coinToss = math.random(1,3)
		if coinToss == 1 then
			thread( CharacterBanishedAudio, deadPlayer )
		end
	end

	-- for VO
	StartTimer({ Name = "KillVOTimer" })
	numRapidKills = numRapidKills + 1

	if TeamA.ObjectIdsLookup[deadPlayerObjectId] then
		numRecentTeamAKills = numRecentTeamAKills + 1
	end
	if TeamB.ObjectIdsLookup[deadPlayerObjectId] then
		numRecentTeamBKills = numRecentTeamBKills + 1
	end

	if numRapidKills >= 2 then
		StopTimer({ Name = "KillVOTimer" })
		SetTimer({ Name = "KillVOTimer", Id = 0 })

		if deathWeapon ~= "CastImp" then
			if numRecentTeamAKills >= 1 and numRecentTeamBKills >= 1 then
				thread( DoubleKOAudio )
			elseif killingPlayer ~= nil then
				if head2head then
					thread( SomeoneMultiKillAudio )
				else
					thread( PlayerMultiKillAudio )
				end
			end
		end

		numRapidKills = 0
		numRecentTeamAKills = 0
		numRecentTeamBKills = 0
	end

	if uncommonMatch and retrievalMap then -- Exception for maps that have no fancy camera to prevent snapping
		if TeamB.ObjectIdsLookup[deadPlayerObjectId] then
			return
		end
		thread(RetrievalCameraPresentation, deadPlayer, deadPlayerObjectId)
	end

	if TeamA.ObjectIdsLookup[booker] then
		thread( CheckKillingObjectiveProgress, killingPlayer, deadPlayer, deathWeapon )
	end

	-- phantom shader
	if deadPlayer ~= SacrificeScorer and PhantomObjectIds[deadPlayerObjectId] then
		SetPlayerUnitProperty({ DestinationId = deadPlayerObjectId, Name = "PhantomOutlineWidth", Value = 0, Delay = 0.48 })
	end

	if not temporarilyRemoveSacrificedScorer then
		-- This was a scoring death, presentation and reset handled separately - should probably return much earlier
		wait(0.38)
		SetOpacity({ Id = deadPlayerObjectId, Fraction = 0, Duration = 0.25 })
		wait(0.26)
		if cameraDirty then
			thread( MatchCameraSelector, "fancyDeath" )
		end
		TeleportToStartingPosition( deadPlayerObjectId )
	end

end }

function RetrievalCameraPresentation( deadPlayer, deadPlayerObjectId )
	PanCamera({ Id = 50049, Duration = 2.5, EaseOut = 0.3 }) -- this is a temp update for a single-death victory in ScenarioRetreival
end

function CheckKillSkills( killingPlayer, killingWeaponName, deadPlayer, deadPlayerId, perfectKill )

	if killingPlayer == nil then
		return
	end

	local staminaRecoverAmount = GetUnitSkillValue( killingPlayer, "KillsRegenerateStamina" )
	if staminaRecoverAmount > 0 then
		thread( GrantStaminaOverTime, killingPlayer.ObjectId, staminaRecoverAmount, 0.5 )
		--FireWeapon({ Name = "InvisibleStaminaRecoverShort", DestinationId =  killingPlayer.ObjectId })
		thread( SecondWindAudio )
		thread( StaminaRechargeFeedback, killingPlayer.ObjectId )
		--thread( SkillProcFeedback, killingPlayer.ObjectId, "KillsRegenerateStamina" )
	end
	CheckKillsRespawnsFriends( killingPlayer )

	if CheckUnitSkillChance( killingPlayer, "AuraKillDetonate" ) and killingPlayer.ObjectId ~= deadPlayerId then
		local resetAuraKill = false
		if string.find( killingWeaponName, "Reset" ) then
			resetAuraKill = true
		end
		if not resetAuraKill then
			FireWeaponFromUnit({ Weapon = "AuraKillDetonate", Id = killingPlayer.ObjectId, DestinationId = deadPlayerId })
			thread( SkillProcFeedback, killingPlayer.ObjectId, "AuraKillDetonate" )
		end
	end

	if deadPlayer == nil then
		return
	end

	if UnitHasSkill( killingPlayer, "PlusEnemyRespawnTime" ) then
		deadPlayer.RespawnTimer = deadPlayer.RespawnTimer * 1.30
	end

	local killCharTeam = League[killingPlayer.TeamIndex]
	if TeamHasSkillDrafted( killCharTeam, "TeamEnemyBanishTime" ) then
		deadPlayer.RespawnTimer = deadPlayer.RespawnTimer + 2.0
	end

	local banishTimeIncrease = GetUnitSkillValue( killingPlayer, "EnemyRespawnIncrease" )
	if banishTimeIncrease > 0 then
		deadPlayer.RespawnTimer = deadPlayer.RespawnTimer + banishTimeIncrease
	end

	if UnitHasSkill(killingPlayer, "BanishScoreBonus" ) then
		thread( CheckBanishScoreBonus, killingPlayer.ObjectId )
	end

	if UnitHasSkill( killingPlayer, "PowerUpKillSkill" ) then
		thread( CheckPowerUpKill, killingPlayer, deadPlayer.ObjectId )
	end

	if perfectKill then
		local powerShotSkillValue = GetUnitSkillValue( killingPlayer, "PowerShotKillSkill" )
		if powerShotSkillValue > 0 then
			local staminaWeapon = "InvisibleStaminaRecover"..powerShotSkillValue
			FireWeapon({ Name = staminaWeapon, DestinationId = killingPlayer.ObjectId })
			thread( SkillProcFeedback, killingPlayer.ObjectId, "PowerShotKillSkill" )
			thread( SecondWindAudio )
		end
	end

end

function GrantStaminaOverTime( unitId, amount, duration )

	local amountGranted = 0
	local numTicks = duration * 60
	local tickAmount = amount / numTicks
	for tickNum = 1, numTicks, 1 do
		GrantStamina({ Id = unitId, Amount = tickAmount })
		wait(0.016)
	end

end

function CheckPowerUpKill( killingCharacter, deadUnitId )
	if killPowerUpCooldown or not RandomChance( 0.5 ) then
		return
	end
	killPowerUpCooldown = true

	local powerUpId = SpawnObstacle({ Name = "PowerUp01", Group = "PowerUps", DestinationId = deadUnitId })
	AdjustZLocation({ Id = powerUpId, Distance = 2000 })
	thread( SkillProcFeedback, killingCharacter.ObjectId, "PowerUpKillSkill" )
	wait(10.5)
	killPowerUpCooldown = false
end

function CheckDeathSkills( deadPlayer, deadCharTeam, booker, killingCharacter )

	if deadPlayer == nil then
		return
	end

	if killingCharacter == nil then
		-- environmental deaths are shorter
		local killerObjectName = GetName({ Id = booker })
		if killerObjectName ~= nil  then
			if string.find( killerObjectName, "_Impassable" ) ~= nil or string.find( killerObjectName, "PoisonPool" ) ~= nil then
				-- VO
				thread( SomeoneBanishedByEnvironmentAudio )
				deadPlayer.RespawnTimer = 2.5
				return
			end
		end
	end

	if retrievalMap or flightSchool then
		deadPlayer.RespawnTimer = 2.5
		return
	end

	if impInstitute then
		deadPlayer.RespawnTimer = 3.25
		return
	end

	if deadPlayer ~= SacrificeScorer then
		local onDeathExplosionItemValue = GetUnitSkillValue( deadPlayer, "OnDeathExplosionItem" )
		if onDeathExplosionItemValue > 0 then
			FireWeaponFromUnit({ Weapon = "OnDeathExplosionItem", Id = deadPlayer.ObjectId })
			thread( SkillProcFeedback, deadPlayer.ObjectId, "OnDeathExplosionItem")
		end
		if UnitHasSkill( deadPlayer, "Beacon" ) and not showdownActive then
			DropBeaconEntrance( deadCharTeam )
			DropBeaconExit( deadCharTeam, deadPlayer.ObjectId )
		end
	end

	if not deadPlayer.ImmuneToSacrifice and deadPlayer == SacrificeScorer then
		-- Not going to respawn so ignore respawn skills below
		return
	end

	if deadPlayer ~= SacrificeScorer and not deadPlayer.BlockRespawn then

		-- No respawn skills ever apply to the SacrificeScorer even if they will respawn

		local skipRespawnSkills = false -- for anything that needs to bypass the rest
		local minRespawnTime = 1.4

		if UnitHasSkill( deadPlayer, "CastFastRespawn" ) and DidSuicide({ Id = deadPlayer.ObjectId }) then
			deadPlayer.RespawnTimer = deadPlayer.RespawnTimer * 0.25
			thread( SkillProcFeedback, deadPlayer.ObjectId, "CastFastRespawn" )
		elseif deadPlayer.Archetype == "PlayerImp" and DidSuicide({ Id = deadPlayer.ObjectId }) then
			deadPlayer.RespawnTimer = deadPlayer.RespawnTimer * 0.60
		end

		if UnitHasSkill( deadPlayer, "JumpRespawn" ) then
			if JumpSpawnEligibleUnits[deadPlayer.ObjectId] ~= nil then
				deadPlayer.RespawnTimer = 0.5
				BlockStartPositionTeleport[deadPlayer.ObjectId] = true
				thread( JumpSpawnTeleport, deadPlayer.ObjectId)
				skipRespawnSkills = true
			end
		end

		if not skipRespawnSkills and UnitHasSkill( deadPlayer, "InstantRespawn" ) then
			local chanceofInstantRespawn = deadPlayer.PlayerAttributeLuck * 0.1
			if RandomChance( chanceofInstantRespawn ) then
				deadPlayer.RespawnTimer = minRespawnTime
				thread( SkillProcFeedback, deadPlayer.ObjectId, "InstantRespawn" )
			end
		end

		if not skipRespawnSkills and CheckUnitSkillChance( deadPlayer, "QuickRespawnSkill" ) then
			deadPlayer.RespawnTimer = minRespawnTime
			thread( SkillProcFeedback, deadPlayer.ObjectId, "QuickRespawnSkill" )
		end

		if KillBuffSpeedRespawnEligibileUnits[deadPlayer.ObjectId] then
			deadPlayer.RespawnTimer = deadPlayer.RespawnTimer * 0.5
		end

		if UnitHasSkill( deadPlayer, "PartnerDeathPowerUp") then
			for k, partnerCharacterData in pairs( deadCharTeam.TeamBench ) do
				if UnitHasSkill( partnerCharacterData, "PartnerDeathPowerUp") and partnerCharacterData.ObjectId ~= deadPlayer.ObjectId and not CheckUnitAlive(partnerCharacterData.ObjectId) then
					EarlyRespawn( partnerCharacterData )
				end
			end
		end

		if TeamHasSkillDrafted( deadCharTeam, "TeamReviveSkill" ) then
			SpawnReviveObject(deadPlayer.ObjectId, deadPlayer)
		end

		if not skipRespawnSkills and banishLongerEvent and TeamA.ObjectIdsLookup[deadPlayer.ObjectId] then
			deadPlayer.RespawnTimer = deadPlayer.RespawnTimer * 2
		end

		if showdownActive then
			deadPlayer.RespawnTimer = 2.0
		end

		if ChallengeData ~= nil and deadPlayer == ChallengeCharacter then
			deadPlayer.RespawnTimer = deadPlayer.RespawnTimer * 0.5
		end
		if deadPlayer.RespawnTimer < minRespawnTime then
			deadPlayer.RespawnTimer = minRespawnTime
		end

	end

	if TeamHasSkillDrafted( deadCharTeam, "TeamHeadWinds" ) then
		thread( CheckHeadWinds, deadCharTeam )
	end

	if TeamHasSkillDrafted( deadCharTeam, "SoleUnitPowerUp" ) and TableLength( deadCharTeam.AssignedCharacters ) >= 2 then
		for k, v in pairs( deadCharTeam.AssignedCharacters ) do
			if Contains( v.Skills, "SoleUnitPowerUp" ) and IsLastUnitStanding( v.ObjectId ) and CheckUnitAlive(v.ObjectId ) then
				FireWeapon({ Name = "InvisibleStaminaRecoverLong", DestinationId = v.ObjectId })
				thread( SkillProcFeedback, v.ObjectId , "SoleUnitPowerUp")
				thread( SecondWindAudio )
				break
			end
		end
	end

end

function JumpSpawnTeleport( deadObjectId )
	PlayOverheadAnimation({ Name = "DraftChoose", DestinationId = JumpSpawnEligibleUnits[deadObjectId], Scale = 0.7, IgnoreOverheadOffset = true  })
	Teleport({ Id = deadObjectId, DestinationId = JumpSpawnEligibleUnits[deadObjectId], Delay = 0.3 })
	wait(0.3)
	MatchCameraSelector( "Teleport" )
	wait(0.1)
	thread( SkillProcFeedback, deadObjectId, "JumpRespawn" )
end


function CheckPairPowerUp( team )
	local pairPowerUpUnits = { }
	for characterKey, character in pairs( team.AssignedCharacters ) do
		if character.ObjectId ~= nil and UnitHasSkill( character, "PairPowerUp") and CheckUnitAlive( character.ObjectId ) then
			pairPowerUpUnits[character.ObjectId] = character
		end
	end

	if TableLength( pairPowerUpUnits ) >= 2 then
		thread( SecondWindAudio )
		for characterId, character in pairs( pairPowerUpUnits ) do
			FireWeapon({ Name = "InvisibleStaminaRecoverLong", DestinationId = character.ObjectId })
			thread( SkillProcFeedback, character.ObjectId , "PairPowerUp")
		end
	end
end

function CheckHeadWinds( deadCharTeam )

	if not IsWholeTeamDead( deadCharTeam ) then
		return
	end

	local gustSide = "Left"
	if deadCharTeam == TeamA then
		gustSide = "Left"
		teamVictimSide = TeamB
		teamVictimObjectIds = TeamBObjectIds
		goalObject = goalA
	else
		gustSide = "Right"
		teamVictimSide = TeamA
		teamVictimObjectIds = TeamAObjectIds
		goalObject = goalB
	end

	if gustSide == "Left" then
		gustClouds = "GliderSpeedlines"
		gustSpeed = 150
		gustAngle = 0

	elseif gustSide == "Right" then
		gustClouds = "CycleSpeedLines"
		gustSpeed = -150
		gustAngle = 180
	end

	ToggleControl({ Names = { "Sprint", "Jump", "Cast", "Taunt" }, Enabled = false })
	StartFlyers({ Name = gustClouds, DrawInFrontOfGroupName = "Events" })

	local charWithSkill = GetCharacterWithSkill( deadCharTeam, "TeamHeadWinds" )
	if charWithSkill ~= nil then
		thread( SkillProcFeedback, charWithSkill.ObjectId, "TeamHeadWinds" )
	end

	while IsWholeTeamDead( deadCharTeam ) do
		FireWeapon({ Name = "HeadWindsSlow", DestinationIds = teamVictimObjectIds })
		--ApplyForce({ Ids = teamVictimObjectIds, Speed = gustSpeed, Angle = gustAngle })
		wait(0.1)
	end

	ToggleControl({ Names = { "Sprint", "Jump", "Cast", "Taunt" }, Enabled = true })
	StopFlyers({ Name = gustClouds })
	StopSpawningFlyers({ Name = gustClouds })

	StopFlyers({ Name = "CycleSpeedLines" })
	StopFlyers({ Name = "GliderSpeedlines" })
end

function CheckKillsRespawnsFriends( killingPlayer )

	if CheckUnitSkillChance( killingPlayer, "KillsRespawnFriends" ) then
		for key, objectId in pairs( League[killingPlayer.TeamIndex].ObjectIds ) do
			local character = GetCharacterTableByObjectId( objectId )
			if character ~= nil and character.RespawnTimer < 99 and not CheckUnitAlive( objectId ) then
				EarlyRespawn( character )
				thread( SkillProcFeedback, killingPlayer.ObjectId, "KillsRespawnFriends" )
				return
			end
		end
	end

end

function DeathPass( deadPlayerData )

	local team = League[deadPlayerData.TeamIndex]

	local passTarget = 0
	for k, objectId in pairs( team.ObjectIds ) do
		local character = GetCharacterTableByObjectId( objectId )
		if character ~= nil and character.ActiveStatus == "Assigned" and CheckUnitAlive( objectId ) then
			passTarget = objectId
		end
	end

	if passTarget > 0 then
		SwitchActiveUnit({ Id = passTarget, PlayerIndex = team.PlayerIndex })
		thread( SkillProcFeedback, deadPlayerData.ObjectId, "PassOnDeath" )
		return true
	end

	return false
end

BlockStartPositionTeleport = { }
function TeleportToStartingPosition( deadPlayerObjectId )

	if BlockStartPositionTeleport[deadPlayerObjectId] then
		BlockStartPositionTeleport[deadPlayerObjectId] = nil
		return
	end

	Teleport({ Id = deadPlayerObjectId, DestinationId = StartingPositions[deadPlayerObjectId] })

	if TeamAObjectIdsLookup[deadPlayerObjectId] then
		SetGoalAngle({ Angle = 0, Id = deadPlayerObjectId, CompleteAngle = true })
	elseif TeamBObjectIdsLookup[deadPlayerObjectId] then
		SetGoalAngle({ Angle = 180, Id = deadPlayerObjectId, CompleteAngle = true })
	end

	SetUnitProperty({ Name = "CollideWithUnits", Value = false, DestinationId = deadPlayerObjectId })
	SetUnitProperty({ Name = "CollideWithObstacles", Value = false, DestinationId = deadPlayerObjectId })

end

function CheckUnitAlive( unitId )
	local playerTable = CharacterCache[unitId]
	if playerTable == nil or playerTable.RespawnTimer < 0 then
		return true
	end
	if playerTable.RespawnTimer > 0 then
		return false
	end
end

function RespawnPlayer( objectId, usePyreShield )

	if stopRespawner then
		return
	end

	if usePyreShield == nil then
		usePyreShield = true
	end

	local deadPlayer = GetCharacterTableByObjectId( objectId )

	SetInvulnerable({ Id = objectId })

	if usePyreShield then
		FireWeapon({ Name = "InvisiblePyreShield", DestinationId = objectId })
	end

	SetScale({ Id = spawnCircle, Fraction = 0.3, Duration = 0 })
	SetScale({ Id = spawnCircle, Fraction = 0, Duration = 0.3, Delay = 2.7 })
	Destroy({ Id = spawnCircle, Delay = 3.1 })

	RespawnUnit( objectId )

	PlayOverheadAnimation({ Name = "UnitRespawnFx", DestinationId = objectId, IgnoreOverheadOffset = true })
	PlayOverheadAnimation({ Name = "DraftSwitchFx", DestinationId = objectId, IgnoreOverheadOffset = true })

	PlaySound({ Name = "/SFX/Match SFX/PlayerRespawnNEW", Id = objectId })

	if deadPlayer ~= nil then
		thread( EmoteRespawning, deadPlayer["VoicePrefix"], objectId )
	end

end

function AllPlayerUnitsDead( lowestRespawnTime, lowestRespawnUnitData )

	if allUnitsDeadPresentation or lowestRespawnTime < 1.5 then
		return
	end
	allUnitsDeadPresentation = true
	SetFlagTrue({ Name = "AllUnitsDead" })

	normalMatchBloom = GetBloomSettingName({ })
	wait(0.05)

	--AdjustFullscreenBloom({ Name = "Memory_06", Duration = 0.1 })
	AdjustColorGrading({ Name = "Banished", Duration = 0.2 })
	thread(ResumeDefaultMatchColorGrade, lowestRespawnTime * 0.2, lowestRespawnTime * 0.7 )

	if lowestRespawnTime > 0 then
		wait(lowestRespawnTime)
	end

	--AdjustFullscreenBloom({ Name = normalMatchBloom, Duration = 0.15 })
	SetFlagFalse({ Name = "AllUnitsDead" })
	wait(0.2)
	allUnitsDeadPresentation = false
end

function CheckAllDeadRespawn( team, justKilled )

	if not IsWholeTeamDead( team ) then
		return
	end

	if GetMapName({ }) == "ScenarioBrawl" or GetMapName({ }) == "ScenarioDefense" then
		return
	end

	local respawnTeam = TeamHasSkillDrafted( team, "AllDeadRespawnTeam" )
	if ConstellationActive("RivalTeamReturnFromBanishment") and team ~= PlayerTeam then
		respawnTeam = true
	end

	-- VO: Team Wipe
	if not DidAnyoneSuicide( team ) then
		if IsMultiplayerMatch() then
			if IsWholeTeamDeadOrRespawning( team, 3 ) then
				thread( TeamFullWipeAudio, team )
			end
		else
			if team == LocalTeam then
				if IsWholeTeamDeadOrRespawning( team, 3 ) then
					thread( AllPlayerUnitsBanishedAudio )
				end
			else
				if IsWholeTeamDeadOrRespawning( team, 3 ) then
					thread( TeamFullWipeAudio, team )
				end
			end
		end
	end

	for k, objectId in pairs( team.ObjectIds ) do
		local character = GetCharacterTableByObjectId( objectId )
		if character ~= nil then
			if respawnTeam or ( UnitHasSkill( character, "AllDeadRespawn" ) and character ~= justKilled ) then
				if character.RespawnTimer < 999 then -- Ignore sacrificed characters
					EarlyRespawn( character )
					if respawnTeam then
						--thread( SkillProcFeedback, objectId, "AllDeadRespawnTeam" )
						thread( MatchQuipAudio )
					end
					if not respawnTeam then
						thread( SkillProcFeedback, objectId, "AllDeadRespawn" )
						thread( MatchQuipAudio )
						ForceSwitchToSoonestRespawn( team )
						return true
					end
				end
			end
		end
	end

	if respawnTeam then
		ForceSwitchToSoonestRespawn( team )
		return true
	end

	return false
end

function IsWholeTeamDead( team )

	if team.AssignedCharacters == nil then
		return false
	end

	for k, character in pairs( team.AssignedCharacters ) do
		if character.RespawnTimer == nil or character.RespawnTimer < 0 then
			return false
		end
	end

	return true

end

function IsWholeTeamDeadOrRespawning( team, time )

	local respawnTime = 1
	if time ~= nil then
		respawnTime = time
	end
	for k, objectId in pairs( team.ObjectIds ) do
		local character = GetCharacterTableByObjectId( objectId )
		if character ~= nil and ( character.RespawnTimer == nil or character.RespawnTimer < respawnTime ) then
			return false
		end
	end

	return true

end

function DidAnyoneSuicide( team )

	for k, objectId in pairs( team.ObjectIds ) do
		local character = GetCharacterTableByObjectId( objectId )
		if character ~= nil and character.RespawnTimer ~= nil and character.RespawnTimer > 0 and DidSuicide({ Id = objectId }) then
			return true
		end
	end

	return false

end


RespawnObjects = { }
function SpawnReviveObject( spawnLocation, deadCharacterData )
	local reviveObject = SpawnObstacle({ Name = "RevivePickup", DestinationId = spawnLocation, Group = "ReviveObjects" })
	SetOpacity({ Id = reviveObject, Fraction = 0.0 })
	SetOpacity({ Id = reviveObject, Fraction = 1.0, Duration = 0.3 })
	RespawnObjects[reviveObject] = deadCharacterData
	local teamColor = GetTeamColorByObjectId( deadCharacterData.ObjectId )
	PlayAnimation({ Name = deadCharacterData.SmallPortrait, DestinationId = reviveObject })
	PlayOverheadAnimation({ Name = "TeamReviveIconFrame", DestinationId = reviveObject, Scale = 1.5, IgnoreOverheadOffset = true })
	PlayOverheadAnimation({ Name = "TeamReviveIconFrameColor", DestinationId = reviveObject, Scale = 1.5, IgnoreOverheadOffset = true, Color = teamColor})
	PlaySound({ Name = "/SFX/Match SFX/FieldRevivePog", Id = reviveObject })
end

function ClearRespawnObjects()
	for id, v in pairs( RespawnObjects ) do
		thread( RemoveRespawnObject, id, 0 )
	end
	RespawnObjects = { }
end

function RemoveRespawnObject( id, fadeOut )

	UseableOff({ Id = id })
	SetOpacity({ Id = id, Fraction = 1.0, Duration = fadeOut })
	if fadeOut > 0 then
		wait(fadeOut)
	end
	StopAnimation({ Name = "TeamReviveIconFrame", DestinationId = id, PreventChain = true })
	StopAnimation({ Name = "TeamReviveIconFrameColor", DestinationId = id, PreventChain = true })
	Destroy({ Id = id })

end

function ClearMatchObjects()
	ClearRespawnObjects()
	RemoveAllBeacons()
	ClearDeathPools()
end

OnUsed{ "ReviveObjects",
	function(triggerArgs)

		local respawnPoint = triggerArgs.triggeredById
		local respawnCharacter = RespawnObjects[respawnPoint]
		local userId = triggerArgs.UserId

		-- unit can't respawn itself
		if respawnCharacter == nil or respawnCharacter.ObjectId == userId then
			return
		end

		-- units must be on the same team
		if GetTeamByCharacterId( userId ) ~= GetTeamByCharacterId( respawnCharacter.ObjectId ) then
			return
		end

		if not IsAlive({ Id = userId }) then
			return
		end

		Teleport({ Id = respawnCharacter.ObjectId, DestinationId = respawnPoint })
		StopAnimation({ Name = "TeamReviveIconFrame", DestinationId = respawnPoint })
		StopAnimation({ Name = "TeamReviveIconFrameColor", DestinationId = respawnPoint })
		EarlyRespawn( respawnCharacter )
		PlaySound({ Name = "/SFX/Match SFX/FieldReviveSFX", Id = userId })
		Destroy({ Id = respawnPoint })
	end
}

function EarlyRespawn( character )

	if character.RespawnTimer <= 0 then
		-- Character isn't dead
		return
	end

	local minDeathSequenceTime = 0.7
	local timeSinceDeath = _worldTime - character.LastDeathTime
	local deathSequenceRemaining = minDeathSequenceTime - timeSinceDeath
	--DebugPrint({ Text = "deathSequenceRemaining = "..deathSequenceRemaining })
	if deathSequenceRemaining > 0 then
		-- Needs to wait if the unit hasn't finished its death sequence yet
		character.RespawnTimer = deathSequenceRemaining
	else
		character.RespawnTimer = 0.02
	end

end

OnControlPressed{ "Pass Cast Jump Sprint",
	function( triggerArgs )
		local team = nil
		if triggerArgs.PlayerIndex == 1 then
			team = TeamA
		else
			team = TeamB
		end
		if team == nil then
			return
		end
		local activeCharacter = GetCharacterTableByObjectId( team.CurrentControlId )
		if activeCharacter == nil then
			return
		end

		if activeCharacter.LocalHumanControlled and IsWholeTeamDead( team ) then
			local soonestRespawnTime = GetSoonestRespawn( team )
			if soonestRespawnTime > 2 then
				MashWhileBanishedAudio()
			end
		end
	end
}

function GetSoonestRespawn( team )
	local soonestRespawnTime = 999
	for k, character in pairs( team.TeamBench ) do
		if character.ActiveStatus == "Assigned" and character.RespawnTimer < soonestRespawnTime then
			soonestRespawnTime = character.RespawnTimer
		end
	end
	return soonestRespawnTime
end

function GiveOnUnitRespawnTimer( unitId, unitMessage )

	if unitId == nil or unitMessage == nil then
		return
	end

	local unitFeedbackFloater = SpawnObstacle({ Name = "InvisibleTarget", Group = "Scripting", DestinationId = unitId, OffsetY = -300 })

	local messageText = tostring(unitMessage)

	DisplayWorldText({ Id = unitFeedbackFloater,
			Text = messageText,
			Justification = "CENTER",
			ShadowBlur = 1, ShadowColor = {0,0,0,1}, ShadowOffset = {3, 3}, OutlineThickness = 1.0, OutlineColor = {0.0, 0.0, 0.0,1},
			Color = {1.0, 0.15, 0.15, 1.0}, Font="AlegreyaSCRegular40", Scale = 0.5 })
	Move({ Id = unitFeedbackFloater, Angle = 90, Speed = 55 })

	PlaySound({ Name = "/SFX/Menu Sounds/WaxDown", Id = unitFeedbackFloater })

	wait(0.9)
	RemoveWorldText({ DestinationId = unitFeedbackFloater, Duration = 0.25 })

	wait(0.5)
	Delete({ Id = unitFeedbackFloater })
end

function RespawnUnit( objectId )

	-- force timer down if respawn ends up being forced
	local playerTable = GetCharacterTableByObjectId( objectId )
	if playerTable ~= nil and playerTable.RespawnTimer > 0 then
		playerTable.RespawnTimer = -1
	end

	for respawnObjectId, characterData in pairs( RespawnObjects ) do
		if characterData.ObjectId == objectId then
			thread( RemoveRespawnObject, respawnObjectId, 0.3 )
			RespawnObjects[respawnObjectId] = nil
			break
		end
	end

	Revive({ Id = objectId })
	SetOpacity({ Id = objectId, Fraction = 1, Duration = 0.3 })
	if PhantomObjectIds[objectId] then
		SetPlayerUnitProperty({ DestinationId = objectId, Name = "PhantomOutlineWidth", Value = PHANTOM_OUTLINE_WIDTH })
	end

	if playerTable ~= nil and playerTable.DeathDefenderUnitId ~= nil then
		Destroy({ Id = playerTable.DeathDefenderUnitId })
		playerTable.DeathDefenderUnitId = nil
	end

	SetUnitProperty({ Name = "CollideWithUnits", Value = true, DestinationId = objectId })
	SetUnitProperty({ Name = "CollideWithObstacles", Value = true, DestinationId = objectId })

	-- if a PlayerTrail respawns, it should get the SoleUnitPowerUp
	if playerTable ~= nil then
		local revivingCharTeam = League[playerTable.TeamIndex]
		if TeamHasSkill( revivingCharTeam, "SoleUnitPowerUp" ) and TableLength( revivingCharTeam.AssignedCharacters ) >= 2 then
			for k, v in pairs( revivingCharTeam.AssignedCharacters ) do
				if Contains( v.Skills, "SoleUnitPowerUp" ) and IsLastUnitStanding( v.ObjectId ) and CheckUnitAlive(v.ObjectId ) then
					FireWeapon({ Name = "InvisibleStaminaRecoverLong", DestinationId = v.ObjectId })
					thread( SkillProcFeedback, v.ObjectId , "SoleUnitPowerUp", PlayerSkillsTable.SoleUnitPowerUp.PickedIcon )
					thread( SecondWindAudio )
					break
				end
			end
		end

		-- imp respawn revive thing
		if UnitHasSkill(playerTable, "ImpRespawn") then
			local skillPick = RandomChance(0.3)
			if skillPick and not playerTable.ImpRespawnLastRevive then
				playerTable.ImpRespawnLastRevive = true
				thread( SkillProcFeedback, playerTable.ObjectId , "MatchMessage_ImpRespawn")
				Impify( playerTable.ObjectId )
			else
				playerTable.ImpRespawnLastRevive = false
				FireWeapon({ Name = "InvisibleStaminaRecoverLong", DestinationId = playerTable.ObjectId })
				thread( SkillProcFeedback, playerTable.ObjectId , "MatchMessage_ImpRespawn")
				thread( SecondWindAudio )
			end
		end
	end
end


-- item-specific
function CheckAuraWithBallCurse( team )
	for k, character in pairs( team.AssignedCharacters ) do
		if UnitHasSkill( character, "AuraWithBall" ) then
			SetWeaponProperty({ DestinationId = character.ObjectId, WeaponName = "CastSmall", PropertyName = "Enabled", Value = false })
			SetWeaponProperty({ DestinationId = character.ObjectId, WeaponName = "CastMedium", PropertyName = "Enabled", Value = false })
			SetWeaponProperty({ DestinationId = character.ObjectId, WeaponName = "CastMediumAlt", PropertyName = "Enabled", Value = false })
			SetWeaponProperty({ DestinationId = character.ObjectId, WeaponName = "CastLarge", PropertyName = "Enabled", Value = false })
			SetWeaponProperty({ DestinationId = character.ObjectId, WeaponName = "CastImp", PropertyName = "Enabled", Value = false })
			SetWeaponProperty({ DestinationId = character.ObjectId, WeaponName = "CastFlying", PropertyName = "Enabled", Value = false })
			SetWeaponProperty({ DestinationId = character.ObjectId, WeaponName = "CastTrail", PropertyName = "Enabled", Value = false })
			SetWeaponProperty({ DestinationId = character.ObjectId, WeaponName = "CastTree", PropertyName = "Enabled", Value = false })
			SetWeaponProperty({ DestinationId = character.ObjectId, WeaponName = "CastMonster", PropertyName = "Enabled", Value = false })
		end
	end
end

function CanGiveNoCastFeedback( unitId )
	if matchAside then
		return false
	end
	if BallCarrierId == unitId then
		return false
	end
	if noCastFeedback then
		return false
	else
		return true
	end
end

OnControlPressed{ "Cast",
	function(triggerArgs)
		local characterData = GetCharacterTableByObjectId(triggerArgs.triggeredById)
		if characterData == nil then
			return
		end
		if UnitHasSkill( characterData, "AuraWithBall" ) and CanGiveNoCastFeedback( triggerArgs.triggeredById ) then
			noCastFeedback = true
			thread(SkillProcFeedback, triggerArgs.triggeredById, "AuraWithBall", nil )
			wait(5)
			noCastFeedback = false
		end
	end
}

-- unit events
function SendUnitToTarget( unitId, locationId, facingId, playerSide )

	if not IsAlive({ Id = unitId }) then
		return
	end

	if playerSide ~= nil then
		SwitchActiveUnit({ Id = unitId, PlayerIndex = playerSide })
	end
	wait(0.1)
	while true do
		if GetDistance({ Id = unitId, DestinationId = locationId }) < 60 then
			break
		else
			DisableInput({ PlayerIndex = 2 })
			Move({ Id = unitId, DestinationId = locationId })
			wait(0.1)
		end
	end
	wait(0.3)
	if facingId ~= nil then
		AngleTowardTarget({ Id = unitId, DestinationId = facingId })
		local playerTable = GetCharacterTableByObjectId(unitId)
		if playerTable ~= nil and GetMapName({ }) == "MatchSiteB" then -- only do emote attack for MatchSiteB event
			thread( EmoteAttack, playerTable["VoicePrefix"], unitId )
		end
	end
	wait(0.1)
end

function ForceResetRespawnState( teamBench )
	for k, playerTable in ipairs( teamBench ) do
		if ShouldForceRespawn( playerTable ) then
			thread( RespawnPlayer, playerTable.ObjectId, false )
		end
	end
end

function ShouldForceRespawn( character )

	if character.ActiveStatus ~= "Assigned" then
		return false
	end

	if character == SacrificeScorer and not character.ImmuneToSacrifice then
		return false
	end

	if character.BlockRespawn then
		return false
	end

	if IsAlive({ Id = character.ObjectId }) then
		return false
	end

	return true

end

function ResetPlayerRespawn()
	ForceResetRespawnState( TeamA.TeamBench )
	ForceResetRespawnState( TeamB.TeamBench )
end

--[[ * RESET/KILL * ]]

local MaxBountyCharacters = { }
OnDestroyAny{ "TeamB",
	function( triggerArgs )
		killer = triggerArgs.KillerId
		killed = triggerArgs.triggeredById
		killerData = GetCharacterTableByObjectId(killer)
		killerBountyValue = 1

		if killer == nil or killed == nil then
			return
		end

		if killerData ~= nil then
			local maxBounty = GetUnitSkillValue( killerData, "BountySkill" )
			if maxBounty > 0 then
				if practiceMatch or ChallengeData ~= nil then
					if not Contains( MaxBountyCharacters, killer ) then
						-- Give warning once
						thread( SkillProcFeedback, killer, "MatchMessage_NoBounty" )
						table.insert( MaxBountyCharacters, killer )
					end
					return
				end
				if matchBounty < maxBounty then
					PlaySound({ Name = "/SFX/Menu Sounds/MakingMoneyChaChing" })
					matchBounty = matchBounty + killerBountyValue
					DepositMoney( killerBountyValue )
					CurrentBounty = killerBountyValue
					thread( SkillProcFeedback, killer, "MatchMessage_Bounty" )
				elseif matchBounty == maxBounty and not Contains( MaxBountyCharacters, killer ) then
					table.insert( MaxBountyCharacters, killer )
					MaxBounty = maxBounty
					thread( SkillProcFeedback, killer, "MatchMessage_BountyMax" )
				end
			end
		end
	end
}

--[[ * PASSING * ]]
OnAnyLoad
{
	function( triggerArgs )
		previousAttached = 0
	end
}

local BallScale =
{
	["PlayerSmall"] = 0.45,
	["PlayerMedium"] = 0.80,
	["PlayerMediumAlt"] = 0.80,
	["PlayerLarge"] = 1.10,
	["PlayerTrail"] = 0.425,
	["PlayerFlying"] = 0.70,
	["PlayerMonster"] = 1.0,
	["PlayerTree"] = 1.0,
	["PlayerImp"] = 0.375,
}

function UpdateBallScale()

	local characterData = CharacterCache[BallCarrierId]
	if characterData == nil then
		return
	end

	local ballScaleMath = BallScale[characterData.Archetype]
	ballScale = ballScaleMath
	SetScale({ Id = ballId, Fraction = ballScale, Duration = 0.3 })
	if ImpifiedUnits[BallCarrierId] then
		SetScale({ Id = ballId, Fraction = 0.4, Duration = 0.3 })
	end
end

function UpdateBallScaleCharacter( character )
	local ballScaleMath = BallScale[character.Archetype]
	ballScale = ballScaleMath
	SetScale({ Id = ballId, Fraction = ballScale, Duration = 0.3 })
	if ImpifiedUnits[BallCarrierId] then
		SetScale({ Id = ballId, Fraction = 0.4, Duration = 0.3 })
	end
end

OnAnyLoad
{
	function( triggerArgs )
		previousTeamAPasser = nil
		currentTeamAPasser = nil
	end
}

OnActiveUnitChanged
{
	function( triggerArgs )

		-- get active character data during tutorial for custom infopanel
		if introMatch and not freeMatch then
			SelectedCharacter = GetCharacterTableByObjectId( triggerArgs.triggeredById )
		end

		if triggerArgs.PlayerIndex == 1 then

			previousTeamAPasser = currentTeamAPasser
			currentTeamAPasser = triggerArgs.triggeredById

			passDistance = GetDistance({ Id = previousTeamAPasser, DestinationId = currentTeamAPasser })

			if not someoneScored then
				local team = GetMatchTeamByCharacterId( currentTeamAPasser )
				team.PassesSincePickup = team.PassesSincePickup + 1

				if team == PlayerTeam then
					local ballCarrierId = BallCarrierId
					if ballCarrierId == currentTeamAPasser then
						thread( MarkObjectiveFailed, "NoPassScore" )
					end
				end

			end
		end

		local newControlId = triggerArgs.triggeredById
		local newControlCharacter = CharacterCache[newControlId]
		local team = League[newControlCharacter.TeamIndex]
		team.PrevControlId = team.CurrentControlId
		team.CurrentControlId = newControlId
		if CheckUnitAlive( team.PrevControlId ) then
			team.PrevControlIdLiving = team.PrevControlId
		end

		if team.PrevControlId == BallCarrierId then
			BallCarrierId = team.CurrentControlId
		end

		local hasBall = false
		local currentPossessorId = BallCarrierId
		if currentPossessorId == newControlId then
			hasBall = true
			UpdateBallScale( newControlCharacter )
		end

		if fancyCamera and not matchAside and not scorePresentation then
			if team == LocalTeam or head2head then
				thread( MatchCameraSelector, "fancyPass" )
			end
		end

		-- Control change SFX and VO
		if newControlCharacter.LocalHumanControlled then
			if hasBall then
				thread( EmoteReceiving, newControlCharacter.VoicePrefix, newControlCharacter.ObjectId )
				PlaySound({ Name = "/SFX/Match SFX/MatchPass", Id = team.PrevControlId })
			else
				if SacrificeScorer == nil then
					PlaySound({ Name = "/SFX/Leftovers/JacketFlutter", Id = team.PrevControlId })
				end
			end
		else
			if hasBall then
				thread( EmoteReceiving, newControlCharacter.VoicePrefix, newControlCharacter.ObjectId )
				PlaySound({ Name = "/SFX/Match SFX/MatchPass", Id = team.PrevControlId })
			else
				-- Silent
			end
		end

		if hasBall and HasUpgrade({ Name = "NoMoveCurse", DestinationId = newControlId }) then
			wait(0.15)
			UnitBallCurse( newControlId )
		end
	end
}

OnTouchdown{ "Token",
	function( triggerArgs )

		BallTossed = false
		BallAirborneDetached = false
		SetInteractProperty({ DestinationId = ballId, Name = "Distance", Value = "120" })

		if tossTutorial then
			UseableOn({ Id = ballId })
		end

		Stop({ Id = ballId })
		Halt({ Id = ballId })

		if ThrowingUnits[throwingUnit] then
			ThrowingUnits[throwingUnit] = false
		end

		if dramaticMomentPlaying then
			thread( InterceptionAudio )
		end

		StopAnimation({ Name = "InkBallAmbientLethal", DestinationId = ballId })

		UseableOn({ Names = { "GoalAButtonGroup", "GoalBButtonGroup" } })

		if not matchAside then
			thread( MatchCameraSelector, "fancyBallTouchdown" )
		end

		SetConfigOption({ Name = "PlayerAIHandicapMultiplier", Value = 1.0 })

	end
}

--[[ * GOALS / SCORING * ]]

OnUsed{ "GoalAButtonGroup",
	function( triggerArgs )

		local userId = triggerArgs.UserId
		lastGoalId = triggerArgs.triggeredById
		UseableOff({ Id = lastGoalId })

		if TeamA.ObjectIdsLookup[userId] then
			OwnGoal( TeamA, lastGoalId )
			return
		end

		if GetZLocation({ Id = triggerArgs.UserId }) > 100 then
			airScore = true
		end

		if ballPossessed then
			-- Sacrifce Score
			BonfireKill( userId, lastGoalId )
		else
			-- Throw Score
			thrownScore = true
			Halt({ Id = ballId })
			ApplyUpwardForce({ Id = ballId, Speed = -2000 })

			CheckGoal( TeamB, TeamA )

		end

		UseableOn({ Names = { "GoalAButtonGroup", "GoalBButtonGroup" } })

	end
}

OnUsed{ "GoalBButtonGroup",
	function( triggerArgs )

		local userId = triggerArgs.UserId
		lastGoalId = triggerArgs.triggeredById

		UseableOff({ Id = lastGoalId })

		if TeamB.ObjectIdsLookup[userId] then
			OwnGoal( TeamB, lastGoalId )
			return
		end

		if GetZLocation({ Id = userId }) > 100 then
			airScore = true
		end

		if ballPossessed then
			-- Sacrifce Score
			BonfireKill( userId, lastGoalId )
		else
			-- Throw Score
			thrownScore = true
			Halt({ Id = ballId })
			ApplyUpwardForce({ Id = ballId, Speed = -2000 })

			CheckGoal( TeamA, TeamB )

			MarkObjectiveComplete( "ThrowScore" )
		end

		UseableOn({ Names = { "GoalAButtonGroup", "GoalBButtonGroup" } })

	end
}

function OwnGoal( goalTeam, goalId )
	if launchBall then
		return
	end
	launchBall = true

	local launchAngle = 0
	if goalTeam == TeamB then
		launchAngle = RandomFloat( 150, 210 )
	else
		launchAngle = RandomFloat( -30, 30 )
	end

	Halt({ Id = ballId })

	local randomForce = RandomFloat( 1500, 1550 )
	local randomUpwardForce = RandomFloat( 550, 650 )
	ApplyUpwardForce({ Id = ballId, Speed = randomUpwardForce })
	ApplyForce({ Id = ballId, Speed = randomForce, Angle = launchAngle })

	goalTeam.PyreAnimation = GetBonfireDouseAnimation( goalTeam )
	PlayAnimation({ Name = goalTeam.PyreAnimation, DestinationId = goalTeam.PyreId })
	PlaySound({ Name = "/SFX/Player Sounds/PlayerProjectile", Id = goalId })

	ColorTeamBonfire( goalTeam )
	wait(0.03)
	RestoreBonfireHouseColors() -- there's a wait somewhere in here

	wait(0.1)
	launchBall = false

end

OnKeyPressed{ "ControlAlt K",
	function( triggerArgs )
		local goalPoint = 40001

		StopAnimation({ DestinationId = goalPoint, PreventChain = true })

		wait(0.1)

		PlayOverheadAnimation({ Name = "Button_A_Goal", DestinationId = goalPoint, Scale = 5.2, IgnoreOverheadOffset = true })
		PlayOverheadAnimation({ Name = "PortraitRespawnFill_Goal", DestinationId = goalPoint, Scale = 1.2, IgnoreOverheadOffset = true, OffsetY = 90 })

		PlayOverheadAnimation({ Name = "PortraitRespawnComplete", DestinationId = goalPoint, Scale = 1.2, IgnoreOverheadOffset = true, OffsetY = 90, Delay = 0.9 })

		wait(0.9)

		StopAnimation({ Name = "Button_A_Goal", DestinationId = goalPoint, PreventChain = true, Delay = 0.5 })
		StopAnimation({ Name = "Button_A2_Goal", DestinationId = goalPoint, PreventChain = true, Delay = 0.5 })
		StopAnimation({ Name = "PortraitRespawnComplete", DestinationId = goalPoint, PreventChain = true, Delay = 0.5 })

	end
}

-- Sacrifice Score Test

OnAnyLoad
{
	function( triggerArgs )

		wait(2)
		sacrificeScore = true
		temporarilyRemoveSacrificedScorer = false
	end
}


function CheckGoal( scoringTeam, scoredOnTeam )

	scorer = BallCarrierId

	if not ballPossessed then
		scorer = throwingUnit
	end

	if sacrificeScore and temporarilyRemoveSacrificedScorer then
		scorer = lastSacrificer
	end

	if scoringTeam.ObjectIdsLookup[scorer] then
		Score( scoringTeam, scoredOnTeam, scorer )
		PostScore( scorer )
	end
end

function BallScorePresentation( scorer, scoredOnTeam )

	-- flag to block some other events
	scorePresentation = true

	SetConfigOption({ Name = "UseOcclusion", Value = false })

	Unattach({ Id = ballId, CenteGeometry = true })
	BallCarrierId = 0

	Move({ Id = ballId, DestinationId = lastGoalId, Duration = 0.3 })

	ClearMatchObjects()

	ClearEffect({ Id = scorer, Name = "StaminaRecover" })

	-- score UI health bar
	if not finalScore or introMatch then
		thread( ShowGoalHealth, scoredOnTeam )
	end

	-- match aside
	if thrownScore then
		wait(0.01)
		thread( MatchAside, { scoredOnTeam.PyreId } )
	else
		thread( MatchAside, bonfireKillFlame )
	end

	Halt({ Id = ballId })
	PutDownGently({ Id = ballId })
	BallTossed = false

	BallScoreFx()
	PyreScoreFx()

	-- ascension music scripting: while ascension music is playing, don't switch off percussion at final score
	if IsAscensionWeek() and finalScore then
		StopAscensionMatchMusicVocals()
		blockMusicChanges = true
	-- standard match-end music scripting
	else
		UpdateMatchMusic( "nopossession" )
	end

	wait(1.8)

	-- throw score info panel
	if thrownScore and not finalScore and not IsMultiplayerMatch() then
		numThrowScores = numThrowScores + 1
		if numThrowScores == 1 then
			wait(3.5)
		end
	end

	if goalHealthBar then
		waitUntil("goalHealthBarComplete")
	end

	thrownScore = false
	scorePresentation = false
	notifyExistingWaiters("scorePresentationComplete")
end

function BallScoreFx()

	if forfeitMatch then
		return
	end


	SetScale({ Id = ballId, Fraction = 0 })
	if TeamA.PyreHealth <= 0 or TeamB.PyreHealth <= 0 then
		-- @hack Needs to cover the throw scale transition still occuring
		SetScale({ Id = ballId, Fraction = 0, Duration = 1.0 })
	end

	if ballScale ~= nil and ballScale > 0 then
		splatScale = (ballScale * 0.8) * 3
	else
		splatScale = 1
	end

	if not thrownScore then

		PlayOverheadAnimation({ Name = "ScoreSplatDecal", Delay = 1.425, DestinationId = lastGoalId,
			IgnoreOverheadOffset = true, OffsetY = -50,
			Scale = splatScale,
			Group = "Shadows" })

	else

		PlaySound({ Name = "/SFX/World Sounds/MapZoomSlow", Id = ballId })
		if roundScore < 30 then
			PlaySound({ Name = "/SFX/Match SFX/PlayerScoreSmall", Delay = 0.05, Id = ballId })
		elseif roundScore >= 30 and roundScore < 60 then
			PlaySound({ Name = "/SFX/Match SFX/PlayerScoreMedium", Delay = 0.05, Id = ballId })
		else
			PlaySound({ Name = "/SFX/Match SFX/PlayerScoreLarge", Delay = 0.05, Id = ballId })
		end

		PlayOverheadAnimation({ Name = "ScoreSplatDecal", Delay = 0.05, DestinationId = lastGoalId,
			IgnoreOverheadOffset = true, OffsetY = -50,
			Scale = splatScale,
			Group = "Shadows" })
	end

end

function GetOpposingTeam( team )

	if team == TeamA then
		return TeamB
	else
		return TeamA
	end

end

function GetBonfireDouseAnimation( team )

	if team.PyreHealth <= 0 then
		return "BonfireDoused"
	end

	local opposingTeam = GetOpposingTeam( team )
	for k, character in pairs( opposingTeam.TeamBench ) do
		if character.ActiveStatus == "Assigned" and character.ScoreValue >= team.PyreHealth then
			-- Match Point
			return "BonfireCriticalDouse"
		end
	end

	if team.PyreHealth <= LOW_PYRE_HEALTH then
		return "BonfireLowDouse"
	end

	return "BonfireDouse"

end

function PyreScoreFx( args, pyreDelay )
	if pyreDelay ~= nil and pyreDelay > 0 then
		wait(pyreDelay)
	end

	local scorerTable = CharacterCache[scorer]
	local scoringTeam = League[scorerTable.TeamIndex]
	local scoredOnTeam = GetOpposingTeam( scoringTeam )

	if forfeitMatch then
		return
	end

	scoredOnTeam.PyreAnimation = GetBonfireDouseAnimation( scoredOnTeam )
	PlayAnimation({ Name = scoredOnTeam.PyreAnimation, DestinationId = scoredOnTeam.PyreId })
	PlayOverheadAnimation({ Name = "ScoreFxDarkGround", DestinationId = scoredOnTeam.PyreId, IgnoreOverheadOffset = true, OffsetY = -200 })

	ColorTeamBonfire( scoredOnTeam )
	thread( RestoreBonfireHouseColors )

	if args == "OnlyFlameVisuals" then
		PlayOverheadAnimation({ Name = "BanishedFxDarkNoAttachedScale", DestinationId = scoredOnTeam.PyreId, IgnoreOverheadOffset = true, Scale = 1 })
		ColorAnimation({ Animation = "BanishedFxDarkNoAttachedScale", ApplyToName = "BanishedFxLightNoAttachedScale", Color = scoredOnTeam.MaskHueRGB })
		PlayOverheadAnimation({ Name = "PyrePlungeDark", DestinationId = scoredOnTeam.PyreId, IgnoreOverheadOffset = true, Scale = 1.0, OffsetY = -15 })
		ColorAnimation({ Animation = "PyrePlungeDark", ApplyToName = "PyrePlungeLightA", Color = scoredOnTeam.MaskHueRGB })
		ColorAnimation({ Animation = "PyrePlungeDark", ApplyToName = "PyrePlungeLightB", Color = scoredOnTeam.MaskHueRGB })
		return
	end

	local scoreFx = "ScoreFxBright"
	local scoreFxExtension = ""
	if roundScore <= 10 then
		-- small score
		scoreFxExtension = "_Low"
		scoreFx = scoreFx..scoreFxExtension
	elseif roundScore <= 20 then
		-- medium score
	else
		-- large score
		scoreFxExtension = "_High"
		scoreFx = scoreFx..scoreFxExtension
	end

	PlayOverheadAnimation({ Name = scoreFx, DestinationId = scoredOnTeam.PyreId, IgnoreOverheadOffset = true })
	ColorAnimation({ Animation = scoreFx, ApplyToName = "ScoreFxBright"..scoreFxExtension, Color = scoredOnTeam.MaskHueRGB })
	ColorAnimation({ Animation = scoreFx, ApplyToName = "ScoreFxFrontBright"..scoreFxExtension, Color = scoredOnTeam.MaskHueRGB })
	ColorAnimation({ Animation = scoreFx, ApplyToName = "ScoreFxFlare"..scoreFxExtension, Color = scoredOnTeam.MaskHueRGB })
	ColorAnimation({ Animation = scoreFx, ApplyToName = "ScoreFxFlareGlow"..scoreFxExtension, Color = scoredOnTeam.MaskHueRGB })

	UpdatePyreScale( scoredOnTeam )

	if scoredOnTeam == TeamA then
		UpdatePyreVolume( goalAFireAmbientLoopId )
	else
		UpdatePyreVolume( goalBFireAmbientLoopId )
	end

end

function SetPyreHealth( team, health )

	team.PyreHealth = health
	team.PrevPyreHealth = team.PyreHealth
	UpdatePyreScale( team )
	if team == TeamA then
		UpdatePyreVolume( goalAFireAmbientLoopId )
	else
		UpdatePyreVolume( goalBFireAmbientLoopId )
	end

end

function UpdatePyreScale( team )

	local fullScaleValue = team.PyreMaxHealth
	if team.PyreAnimation == "BonfireCriticalDouse" or team.PyreAnimation == "BonfireLowDouse" then
		fullScaleValue = LOW_PYRE_HEALTH
	end

	local goalScaleFraction = Lerp( 0.25, 1, team.PyreHealth / fullScaleValue )
	if goalScaleFraction <= 0 then
		goalScaleFraction = 0.00001
	elseif goalScaleFraction > 1.0 then
		goalScaleFraction = 1.0
	end

	if finalScore then
		SetScale({ Id = team.PyreId, Fraction = 1.0, Duration = 1.33 })
	else
		SetScale({ Id = team.PyreId, Fraction = goalScaleFraction, Duration = 1.33  })
	end

end

function UpdatePyreVolume( soundId )

	local soundName = "/SFX/Match SFX/FireAmbientLoopMATCH"
	local paramName = "FireSizeA"
	local team = TeamA
	local goalId = goalA
	if soundId == goalBFireAmbientLoopId then
		soundName = "/SFX/Match SFX/FireAmbientLoopMATCH_2"
		paramName = "FireSizeB"
		team = TeamB
		goalId = goalB
	end

	if team.PyreHealth <= 0 then
		StopSound({ Name = soundName, Duration = 0.3 })
		return
	end

	local pyreHealthFraction = team.PyreHealth / team.PyreMaxHealth
	SetSoundCueValue({ Name = paramName, Id = soundId, Value = pyreHealthFraction, Duration = 0.3 })

	-- @refactor Hijacking the sound hook to update use radius
	local MinUseDistance = 75
	local MaxUseDistance = 150
	local useDistance = Lerp( MinUseDistance, MaxUseDistance, pyreHealthFraction )
	SetInteractProperty({ DestinationId = goalId, Name = "Distance", Value = useDistance })
	SetInteractProperty({ DestinationId = goalId, Name = "AutoUseDistance", Value = useDistance })

end

DEFAULT_PYRE_HEALTH = 100
LOW_PYRE_HEALTH = 60

OnAnyLoad{ function( triggerArgs )

	roundScore = 0
	previousRoundScore = 0
	firstScoreAudioCanPlay = true

	SetConfigOption({ Name = "BlockedCellImpulse", Value = 90 })
	SetConfigOption({ Name = "BlockedCellImpulseUpward", Value = 0 })
	SetConfigOption({ Name = "BlockedCellForceTargetId", Value = 0 })

end }

local stardustEarned = 0
local goldScoreEarned = 0
function Score( scoringTeam, scoredOnTeam, scorer )

	someoneScored = true

	wyrmsDebating = false

	DisableInput({ PlayerIndex = 1 })
	DisableInput({ PlayerIndex = 2 })
	SetInvulnerable({ Ids = TeamAObjectIds })
	SetInvulnerable({ Ids = TeamBObjectIds })

	if matchFinished then
		-- Cannot score once the game is over otherwise the match can be recorded twice
		return
	end

	local scoringCharacter = CharacterCache[scorer]
	local opposingTeam = scoredOnTeam

	if CurrentTutorialBox == "Info_ControlsUI" then
		HideTutorialBox()
	end

	stopRespawner = true
	lastScorer = scorer
	if temporarilyRemoveSacrificedScorer then
		SacrificeScorer = scoringCharacter
		ThrowScorer = nil
		thread( MarkObjectiveFailed, "BreakAuraLink" )
		thread( MarkObjectiveFailed, "DefeatingTrees01" )
	elseif thrownScore then
		ThrowScorer = scoringCharacter
		SacrificeScorer = nil
		thread( MarkObjectiveComplete, "BreakAuraLink" )
		thread( MarkObjectiveFailed, "DefeatingTrees01" )
	end

	if scoringCharacter ~= nil then
		if temporarilyRemoveSacrificedScorer then
			roundScore = scoringCharacter.ScoreValue + GetScoreValueBonus( scoringCharacter, false, false )
		else
			roundScore = tossValue
			tossValue = 0
		end

		-- these multiplier skills must go last
		if CheckUnitSkillChance( scoringCharacter, "RandomScoreSkill" ) then
			if CoinFlip() then
				roundScore = roundScore * 2
				roundScore = math.floor(roundScore + 0.5)
				thread( SkillProcFeedback, scoringCharacter.ObjectId, "MatchMessage_RandomScoreSkillGood" )
			else
				roundScore = roundScore * 0.5
				roundScore = math.floor(roundScore - 0.5)
				thread( SkillProcFeedback, scoringCharacter.ObjectId, "MatchMessage_RandomScoreSkillBad" )
			end
		end

		local maxScoreBounty = 3
		local scoreBountyValue = 5
		if UnitHasSkill( scoringCharacter, "BountyScoreSkill" ) then
			if practiceMatch then
				if not Contains( MaxBountyCharacters, killer ) then
					-- Give warning once
					thread( SkillProcFeedback, scoringCharacter.ObjectId, "MatchMessage_NoBounty" )
					table.insert( MaxBountyCharacters, killer )
				end
			elseif goldScoreEarned < maxScoreBounty then
				goldScoreEarned = goldScoreEarned + 1
				PlaySound({ Name = "/SFX/Menu Sounds/MakingMoneyChaChing" })
				DepositMoney( scoreBountyValue )
				CurrentBounty = scoreBountyValue
				thread( SkillProcFeedback, scoringCharacter.ObjectId, "MatchMessage_Bounty" )
			else
				MaxBounty = (maxScoreBounty * scoreBountyValue)
				thread( SkillProcFeedback, scoringCharacter.ObjectId, "MatchMessage_BountyMax" )
			end
		end

		if introMatch and scoringCharacter.TeamIndex == PracticeTeamIndex then
			roundScore = 10
		end

		scoringCharacter.PointsLastMatch = scoringCharacter.PointsLastMatch + roundScore
		scoringCharacter.PointsAllTime = scoringCharacter.PointsAllTime + roundScore
	end

	scoredOnTeam.PrevPyreHealth = scoredOnTeam.PyreHealth
	scoredOnTeam.PyreHealth = scoredOnTeam.PyreHealth - roundScore
	CheckPyreLastHitShield( scoredOnTeam )
	if scoredOnTeam.PyreHealth < 0 then
		scoredOnTeam.PyreHealth = 0
	end
	scoringTeam.NumScores = scoringTeam.NumScores + 1
	if scoringTeam == TeamA then
		CheckScoringObjectiveProgress( scoringCharacter )
		CheckScoringAchievements( scoringCharacter, roundScore )
	end

	MatchScoreAudio( scoringCharacter, roundScore )

	firstScoreAudioCanPlay = false
	UpdateMatchMusic()

	-- SFX: scoring
	-- final score

	if not forfeitMatch then
		if TeamA.PyreHealth <= 0 or TeamB.PyreHealth <= 0 then
			PlaySound({ Name = "/SFX/Match SFX/FinalScorePyreDouse" })
		else
			if roundScore <= 10 then
				-- small score
				PlaySound({ Name = "/SFX/Menu Sounds/SunMoonInteract" })
			elseif roundScore <= 20 then
				-- medium score
				PlaySound({ Name = "/SFX/Menu Sounds/GuideBookChapterUnlock" })
			else
				-- large score
				PlaySound({ Name = "/SFX/Match SFX/GoalScoredNEW" })
			end
		end
	else
		PlaySound({ Name = "/SFX/World Sounds/QuickSnap" })
	end

	-- lategame music
	if not lateGameMusic then
		if ( TeamA.PyreHealth <= 30 ) or ( TeamB.PyreHealth <= 30 ) then
			lateGameMusic = true
			UpdateMatchMusic( "lategame" )
		end
	end

	-- goal heal skill
	local goalHealSkillValue = GetUnitSkillValue( scoringCharacter, "GoalHealSkill" )
	if goalHealSkillValue > 0 then
		if scoringTeam.PyreHealth < scoringTeam.PyreMaxHealth then
			scoringTeam.PyreHealth = scoringTeam.PyreHealth + goalHealSkillValue
			if scoringTeam.PyreHealth > scoringTeam.PyreMaxHealth then
				scoringTeam.PyreHealth = scoringTeam.PyreMaxHealth
			end
			UpdatePyreScale( scoringTeam )
			thread( SkillProcFeedback, scoringCharacter.ObjectId, "GoalHealSkill", nil, goalHealSkillValue )
		end
	end

	-- goal lifesteal skill
	local goalLifeStealSkillValue = GetUnitSkillValue( scoringCharacter, "GoalLifeStealSkill" )
	if goalLifeStealSkillValue > 0 then
		if scoringTeam.PyreHealth < scoringTeam.PyreMaxHealth then
			local healAmount = round(roundScore * goalLifeStealSkillValue * 0.01)
			scoringTeam.PyreHealth = scoringTeam.PyreHealth + healAmount
			if scoringTeam.PyreHealth > scoringTeam.PyreMaxHealth then
				scoringTeam.PyreHealth = scoringTeam.PyreMaxHealth
			end
			UpdatePyreScale( scoringTeam )
			thread( SkillProcFeedback, scoringCharacter.ObjectId, "GoalLifeStealSkill", nil, healAmount )
		end
	end

	-- goal heal constellation
	if scoringTeam == TeamB and ConstellationActive( "RivalPyreRegenerate" ) then
		if scoringTeam.PyreHealth < scoringTeam.PyreMaxHealth then
			scoringTeam.PyreHealth = scoringTeam.PyreHealth + 5
			if scoringTeam.PyreHealth > scoringTeam.PyreMaxHealth then
				scoringTeam.PyreHealth = scoringTeam.PyreMaxHealth
			end
			UpdatePyreScale( scoringTeam )
			--thread( SkillProcFeedback, scoringCharacter.ObjectId, "GoalHealConstellation" )
		end
	end

	if TeamHasSkillDrafted( scoringTeam, "PyreLastHitRegenerateTeam" ) then
		if scoringTeam.PyreHealth < scoringTeam.PyreMaxHealth and scoringTeam.PyreShieldProcced then
			scoringTeam.PyreHealth = scoringTeam.PyreHealth + 10
			if scoringTeam.PyreHealth > scoringTeam.PyreMaxHealth then
				scoringTeam.PyreHealth = scoringTeam.PyreMaxHealth
			end
			UpdatePyreScale( scoringTeam )
			thread( SkillProcFeedback, scoringCharacter.ObjectId, "PyreLastHitRegenerateTeam" )
		end
	end

	-- VO: scoring
	if TeamB.PyreHealth <= 0 then
		finalScore = true
		-- count win streak
		if TeamA.SuccessiveWins == nil then
			TeamA.SuccessiveWins = 1
		else
			TeamA.SuccessiveWins = TeamA.SuccessiveWins + 1
		end
		-- ascension music scripting: re-enable archjustice VO for final score if it is off
		if ascensionMatch and blockMatchVO then
			blockMatchVO = false
		end
		-- forfeit match scripting: re-enable archjustice VO for final score
		if forfeitMatch then
			blockMatchVO = false
		end
		if not introMatch then
			thread( MatchOutroAudio, "win" )
		end
	elseif TeamA.PyreHealth <= 0 then
		finalScore = true
		-- reset win streak
		if TeamA.SuccessiveWins ~= nil then
			TeamA.SuccessiveWins = nil
		end
		-- ascension music scripting: re-enable archjustice VO for final score if it is off
		if ascensionMatch and blockMatchVO then
			blockMatchVO = false
		end
		if not introMatch then
			thread( MatchOutroAudio, "lose" )
		end
	end
	BallScorePresentation( scorer, scoredOnTeam )

end

function GetScoreValueBonus( scoringCharacter, throwing, skipSkillProc )

	local scoreBonus = 0
	local scoringTeam = League[scoringCharacter.TeamIndex]
	local opposingTeam = GetOpposingTeam( scoringTeam )

	if ScoreValueCheat ~= nil then
		scoreBonus = ScoreValueCheat
	end

	if throwing then
		local pyreDamageBonusValue = GetUnitSkillValue( scoringCharacter, "ThrowScoreSkill" )
		if pyreDamageBonusValue > 0 then
			scoreBonus = scoreBonus + pyreDamageBonusValue
			if not skipSkillProc then
				thread( SkillProcFeedback, scoringCharacter.ObjectId, "ThrowScoreSkill", nil, pyreDamageBonusValue )
			end
		end
	else
		local pyreDamageBonusValue = GetUnitSkillValue( scoringCharacter, "SacrificeScoreSkill" )
		if pyreDamageBonusValue > 0 then
			scoreBonus = scoreBonus + pyreDamageBonusValue
			thread( SkillProcFeedback, scoringCharacter.ObjectId, "SacrificeScoreSkill", nil, pyreDamageBonusValue )
		end
	end

	if tauntScorers[scoringCharacter.ObjectId] then
		tauntValue = GetUnitSkillValue( scoringCharacter, "TauntScoreSkill" )
		scoreBonus = scoreBonus + tauntValue
		thread( SkillProcFeedback, scoringCharacter.ObjectId, "TauntScoreSkill", nil, tauntValue )
	end

	if airScore then
		local flyingSkill = GetUnitSkillValue( scoringCharacter, "UniqueFlyingSkill" )
		if flyingSkill > 0 then
			scoreBonus = scoreBonus + flyingSkill
			thread( SkillProcFeedback, scoringCharacter.ObjectId, "UniqueFlyingSkill", nil, flyingSkill )
		end
	end

	if UnitHasSkill( scoringCharacter, "AnyScoreSkill" ) then
		scoreBonus = scoreBonus + 10
		if not skipSkillProc then
			thread( SkillProcFeedback, scoringCharacter.ObjectId, "AnyScoreSkill", nil, 10 )
		end
	end

	if BanishScorers[scoringCharacter.ObjectId] then
		scoreBonus = scoreBonus + 10
		thread( SkillProcFeedback, scoringCharacter.ObjectId, "BanishScoreBonus", nil, 10 )
	end

	if raining then
		scoreBonus = scoreBonus + 5
		thread( SkillProcFeedback, scoringCharacter.ObjectId, "RainSkill" )
	end

	if scoringTeam.PyreHealth < opposingTeam.PyreHealth then
		local losingScoreSkillValue = GetUnitSkillValue( scoringCharacter, "LosingScoreSkill" )
		if losingScoreSkillValue > 0 then
			scoreBonus = scoreBonus + losingScoreSkillValue
			if not skipSkillProc then
				thread( SkillProcFeedback, scoringCharacter.ObjectId, "LosingScoreSkill", nil, losingScoreSkillValue )
			end
		end
	elseif scoringTeam.PyreHealth > opposingTeam.PyreHealth then

		if TeamHasSkillDrafted( opposingTeam, "LosingScoreScoreDebuff" ) then
			scoreBonus = scoreBonus - 10
			if not skipSkillProc then
				thread( SkillProcFeedback, scoringCharacter.ObjectId, "LosingScoreScoreDebuff", nil, 10 )
			end
		end

		local winningScoreSkillValue = GetUnitSkillValue( scoringCharacter, "WinningScoreSkill" )
		if winningScoreSkillValue > 0 then
			scoreBonus = scoreBonus + winningScoreSkillValue
			if not skipSkillProc then
				thread( SkillProcFeedback, scoringCharacter.ObjectId, "WinningScoreSkill", nil, winningScoreSkillValue )
			end
		end

	end

	local pyreDamageBonusValue = GetUnitSkillValue( scoringCharacter, "PyreDamageSkill" )
	if pyreDamageBonusValue > 0 then
		scoreBonus = scoreBonus + pyreDamageBonusValue
		if not skipSkillProc then
			thread( SkillProcFeedback, scoringCharacter.ObjectId, "PyreDamageSkill", nil, pyreDamageBonusValue )
		end
	end

	if scoringTeam == TeamB and ConstellationActive("RivalPyreDamage") then
		scoreBonus = scoreBonus + 5
		--thread( SkillProcFeedback, scoringCharacter.ObjectId, "RivalPyreDamage" )
	end

	return scoreBonus

end

function WinningScore()

	matchFinished = true

	ClearDefenders()
	Destroy({ Name = "SuicideImpGroup" })

	if netMPMatch then
		return
	end

	if not practiceMatch and not introMatch and not netMPMatch then
		RecordMatch( TeamA.LeagueIndex, TeamB.LeagueIndex )
	else
		SetPostMatchActiveStatus( TeamA, TeamB )
	end

	SetInvulnerable({ Ids = TeamAObjectIds })
	SetInvulnerable({ Ids = TeamBObjectIds })
	DisableInput({ PlayerIndex = 1 })
	DisableInput({ PlayerIndex = 2 })

	if introMatch then
		PlayMatchAmbience()
		thread( PlayIntroMatchSpeech, "lastscore" )
	else
		SwitchAmbience( "/Ambience/StarPickerAmbienceLoopQUIET" )
	end

	UpdateMatchMusic( "end" )

	if not introMatch then
		wait(1.3)
		if TeamA.PyreHealth > TeamB.PyreHealth then
			MatchConclusionPresentation( TeamA )
		else
			MatchConclusionPresentation( TeamB )
		end
	end

	wait(1.6)

	if introMatch then
		IntroMatchExitPresentation()
	end

end

OnAnyLoad
{
	function( triggerArgs )
		numThrowScores = numThrowScores or 0
		PersistVariable({ Name = "numThrowScores" })
	end
}

function IntroMatchExitPresentation()
	ClearCameraClamp({  })
	blockFancyCamera = true
	thread(ShowInputDisabledPresentation)
	AdjustFullscreenBloom({ Name = "Default", Duration = 5, Delay = 3 }) -- matching start pan down bloom
	StopAnimation({ Name = "ArrowIndicator3", DestinationIds = TeamAObjectIds })
	StopAnimation({ Name = "ArrowIndicator3", DestinationIds = TeamBObjectIds })
	--PanCamera({ Id = 40006, Duration = 15, EaseIn = 1, EaseOut = 4, OffsetY = -900, Retarget = true })
	LockCamera({ Id = 50022, Duration = 14, OffsetY = -2500 })
	FocusCamera({ Fraction = 0.4, Duration = 17, ZoomType = "Ease" })
	-- arbitrary wait for temp audio spacing
	wait(15)
end

function PickPreSetRuneCover( runeCover )

	local preSetRuneCover = { "CoverRunes01", "CoverRunes02", "CoverRunes03", "CoverRunes04", "CoverRunes05" }
	runeBaseString = GetRandomValue( preSetRuneCover )
	if runeCover ~= nil then
		runeBaseString = runeCover
	end
	local runeStringA = runeBaseString.."A"
	local runeStringB = runeBaseString.."B"
	local runeStringC = runeBaseString.."_Lighting"
	local runeStringD = runeBaseString.."_Anims"
	local runeStringE = runeBaseString.."_BGWriting"
	runeNames = { runeStringA, runeStringB, runeStringC, runeStringD, runeStringE }

	Activate({ Names = runeNames })
	SetOpacity({ Names = runeNames, Fraction = 0 })
	SetOpacity({ Names = runeNames, Fraction = 1, Duration = 0.4 })

	if TeamA ~= nil then
		--SetColor({ Name = runeStringA, Color = TeamA.MaskHueRGB, Duration = 0.4 })
	end

	if TeamB ~= nil then
		--SetColor({ Name = runeStringB, Color = TeamB.MaskHueRGB, Duration = 0.4 })
	end

end

OnKeyPressed{ "Alt J", Name = "PickPreSetRuneCover",
	function(triggerArgs)
		Delete({ Names = runeNames })
		if runeBaseString == nil then
			PickPreSetRuneCover( "CoverRunes01" )
		elseif runeBaseString == "CoverRunes01" then
			PickPreSetRuneCover( "CoverRunes02" )
		elseif runeBaseString == "CoverRunes02" then
			PickPreSetRuneCover( "CoverRunes03" )
		elseif runeBaseString == "CoverRunes03" then
			PickPreSetRuneCover( "CoverRunes04" )
		elseif runeBaseString == "CoverRunes04" then
			PickPreSetRuneCover( "CoverRunes05" )
		elseif runeBaseString == "CoverRunes05" then
			PickPreSetRuneCover( "CoverRunes01" )
		end
	end
}


function SetupHouseColors()

	SetupBonfireHouseColors()
	SetupUnitHouseColors()

	if TeamA ~= nil then
		SetLightBarColor({ PlayerIndex = 1, Color = TeamA.MaskHueRGB })
	end
	if TeamB ~= nil then
		SetLightBarColor({ PlayerIndex = 2, Color = TeamB.MaskHueRGB })
	end

end

function SetupBonfireHouseColors()

	if TeamA ~= nil then
		ColorTeamBonfire( TeamA )
	end
	if TeamB ~= nil then
		ColorTeamBonfire( TeamB )
	end

end

function ColorTeamBonfire( team )

	SetColor({ Id = team.PyreId, Animation = "BonfireColorA", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireColorB", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireParticles", Color = team.MaskHueRGB2 })

	SetColor({ Id = team.PyreId, Animation = "BonfireLowColorA", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireLowColorB", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireLowParticles", Color = team.MaskHueRGB2 })

	SetColor({ Id = team.PyreId, Animation = "BonfireCriticalColorA", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireCriticalColorB", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireCriticalParticles", Color = team.MaskHueRGB2 })

	SetColor({ Id = team.PyreId, Animation = "BonfireSecondaryLightA", Color = team.MaskHueRGB2 })
	SetColor({ Id = team.PyreId, Animation = "BonfireSecondaryLightB", Color = team.MaskHueRGB2 })

	SetColor({ Id = team.PyreId, Animation = "BonfireIgnite", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireIgniteColorA", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireIgniteColorB", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireIgniteParticles", Color = team.MaskHueRGB2 })

	SetColor({ Id = team.PyreId, Animation = "BonfireDouseColorB", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireDouseColorA", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireDouseParticles", Color = team.MaskHueRGB2 })

	SetColor({ Id = team.PyreId, Animation = "BonfireLowDouseColorB", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireLowDouseColorA", Color = team.MaskHueRGB })

	SetColor({ Id = team.PyreId, Animation = "BonfireCriticalDouseColorB", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireCriticalDouseColorA", Color = team.MaskHueRGB })

	SetColor({ Id = team.PyreId, Animation = "BonfireDousedA", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireDousedB", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireDousedSecondary", Color = team.MaskHueRGB2 })
	SetColor({ Id = team.PyreId, Animation = "BonfireDousedFlare", Color = team.MaskHueRGB })

end

function RestoreBonfireHouseColors()
	wait(1.34)
	SetupBonfireHouseColors()
end

function SetupUnitHouseColors()

	if TeamA ~= nil then
		SetPlayerUnitProperty({ DestinationIds = TeamAObjectIds, Name = "MaskHue", Value = TeamA.MaskHue })
		SetPlayerUnitProperty({ DestinationIds = TeamAObjectIds, Name = "MaskHue2", Value = TeamA.MaskHue2 })
		SetPlayerUnitProperty({ DestinationIds = TeamAObjectIds, Name = "MaskSaturationAddition", Value = TeamA.MaskSaturationAddition })
		SetPlayerUnitProperty({ DestinationIds = TeamAObjectIds, Name = "MaskSaturationAddition2", Value = TeamA.MaskSaturationAddition2 })
		SetPlayerUnitProperty({ DestinationIds = TeamAObjectIds, Name = "MaskValueAddition", Value = TeamA.MaskValueAddition })
		SetPlayerUnitProperty({ DestinationIds = TeamAObjectIds, Name = "MaskValueAddition2", Value = TeamA.MaskValueAddition2 })
	end

	if TeamB ~= nil then
		SetPlayerUnitProperty({ DestinationIds = TeamBObjectIds, Name = "MaskHue", Value = TeamB.MaskHue })
		SetPlayerUnitProperty({ DestinationIds = TeamBObjectIds, Name = "MaskHue2", Value = TeamB.MaskHue2 })
		SetPlayerUnitProperty({ DestinationIds = TeamBObjectIds, Name = "MaskSaturationAddition", Value = TeamB.MaskSaturationAddition })
		SetPlayerUnitProperty({ DestinationIds = TeamBObjectIds, Name = "MaskSaturationAddition2", Value = TeamB.MaskSaturationAddition2 })
		SetPlayerUnitProperty({ DestinationIds = TeamBObjectIds, Name = "MaskValueAddition", Value = TeamB.MaskValueAddition })
		SetPlayerUnitProperty({ DestinationIds = TeamBObjectIds, Name = "MaskValueAddition2", Value = TeamB.MaskValueAddition2 })
	end

end

function SetupSingleUnitHouseColors( character, objectId )

	local team = League[character.TeamIndex]
	local colorObject = team

	-- Setup derived team RGB
	SetPlayerUnitProperty({ DestinationId = objectId, DeriveRGB = true, Name = "MaskHue", Value = colorObject.MaskHue })
	SetPlayerUnitProperty({ DestinationId = objectId, DeriveRGB = true, Name = "MaskHue2", Value = colorObject.MaskHue2 })
	SetPlayerUnitProperty({ DestinationId = objectId, DeriveRGB = true, Name = "MaskSaturationAddition", Value = colorObject.MaskSaturationAddition })
	SetPlayerUnitProperty({ DestinationId = objectId, DeriveRGB = true, Name = "MaskSaturationAddition2", Value = colorObject.MaskSaturationAddition2 })
	SetPlayerUnitProperty({ DestinationId = objectId, DeriveRGB = true, Name = "MaskValueAddition", Value = colorObject.MaskValueAddition })
	SetPlayerUnitProperty({ DestinationId = objectId, DeriveRGB = true, Name = "MaskValueAddition2", Value = colorObject.MaskValueAddition2 })

	if character.MaskHue ~= nil then
		-- Then apply seperate character color if exists
		colorObject = character
		SetPlayerUnitProperty({ DestinationId = objectId, DeriveRGB = false, Name = "MaskHue", Value = colorObject.MaskHue })
		SetPlayerUnitProperty({ DestinationId = objectId, DeriveRGB = false, Name = "MaskHue2", Value = colorObject.MaskHue2 })
		SetPlayerUnitProperty({ DestinationId = objectId, DeriveRGB = false, Name = "MaskSaturationAddition", Value = colorObject.MaskSaturationAddition })
		SetPlayerUnitProperty({ DestinationId = objectId, DeriveRGB = false, Name = "MaskSaturationAddition2", Value = colorObject.MaskSaturationAddition2 })
		SetPlayerUnitProperty({ DestinationId = objectId, DeriveRGB = false, Name = "MaskValueAddition", Value = colorObject.MaskValueAddition })
		SetPlayerUnitProperty({ DestinationId = objectId, DeriveRGB = false, Name = "MaskValueAddition2", Value = colorObject.MaskValueAddition2 })
	end

end

function ColorBonfire( team )

	if team == nil then
		return
	end

	SetColor({ Id = team.PyreId, Animation = "BonfireColorA", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireColorB", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireParticles", Color = team.MaskHueRGB2 })
	SetColor({ Id = team.PyreId, Animation = "BonfireSecondaryLightA", Color = team.MaskHueRGB2 })
	SetColor({ Id = team.PyreId, Animation = "BonfireSecondaryLightB", Color = team.MaskHueRGB2 })
	SetColor({ Id = team.PyreId, Animation = "BonfireIgnite", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireIgniteColorA", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireIgniteColorB", Color = team.MaskHueRGB })
	SetColor({ Id = team.PyreId, Animation = "BonfireIgniteParticles", Color = team.MaskHueRGB2 })

end

function ColorMeteorite( team, meteorite )
	if team == nil then
		return
	end

	SetColor({ Id = meteorite, Animation = "Meteorite", Color = team.MaskHueRGB })
	SetColor({ Id = meteorite, Animation = "MeteoriteAir", Color = team.MaskHueRGB })
	SetColor({ Id = meteorite, Animation = "MeteoriteAirDark", Color = team.MaskHueRGB })
end

function ShowGoalHealth( team, specialCase, waitTime )

	-- exception for roundswitch
	if specialCase == "RoundSwitch" then
		if Contains(TeamBObjectIds, lastScorer) then -- don't show it if you were just scored on
			return
		elseif introMatch then -- don't show it in the intro match
			return
		end
	end

	goalHealthBar = true
	SetMenuOptions({ Name = "InGameUI", Properties = { Run = "AutoFadeGoalHealthOff" } })

	local goalComponent = nil

	local goalTarget = team.GoalHealthTarget
	if specialCase == "PlayerIntro" then
		goalTarget = team.GoalHealthTargetIntro
	elseif specialCase == "OpponentIntro" then
		goalTarget = team.GoalHealthTargetIntro
	end

	if team == TeamB then
		goalComponent = "TeamBGoal"
		previousGoalHealth = TeamB.PrevPyreHealth
	else
		goalComponent = "TeamAGoal"
		previousGoalHealth = TeamA.PrevPyreHealth
	end

	if waitTime ~= nil then
		wait( waitTime )
	elseif specialCase == "PlayerIntro" then
		wait(0.3)
	elseif specialCase == "OpponentIntro" then
		wait(2.3)
	elseif specialCase == "RoundSwitch" then
		wait(0.05)
	elseif specialCase ~= "Rain" then
		wait(0.3)
	end

	if waitTime == nil and specialCase ~= "RoundSwitch" then
		wait(1.05)
	end

	SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthBacking", Properties = { FadeTarget = 1.0, AttachToObjectId = goalTarget } })
	SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthIcon", Properties = { Graphic = team.PyreIcon, FadeTarget = 1.0 } })
	--SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { AttachTo = goalComponent.."HealthBacking" } })
	SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { AttachToObjectId = goalTarget } })

	local goalHealthTicks = 0
	if specialCase == "RoundSwitch" then
		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthText", Properties = { SetText = team.PyreHealth, FadeTarget = 1.0 } })
		--AttachToObjectId = team.GoalButtonId,
		wait(0.8)
	elseif specialCase == "PlayerIntro" or specialCase == "OpponentIntro" then
		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthText", Properties = { SetText = 0, FadeTarget = 1.0 } })
		--AttachToObjectId = team.GoalButtonId
		wait(0.2)
		PlaySound({ Name = "/SFX/Match SFX/PyreTickUpREDO2choir", Id = team.GoalButtonId })
		local goalHealth = 0
		while goalHealth <= team.PyreHealth do
			goalHealthTicks = goalHealthTicks + 1
			SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthText", Properties = { SetText = goalHealth, FadeTarget = 1.0 } })
			wait(0.01)
			goalHealth = goalHealth + 1
		end
	elseif previousGoalHealth > team.PyreHealth then
		-- Damage
		local damageAmount = math.abs( previousGoalHealth - team.PyreHealth )
		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthText", Properties = { SetText = previousGoalHealth, FadeTarget = 1.0 } })
		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { SetText = "-"..damageAmount, FadeOpacity = 1.0, FadeTarget = 1.0} })

		if roundScore <= 10 then
			SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { ScaleTarget = 1.5, ScaleSpeed = 999.0, TextRed = 0.745, TextGreen = 0.110, TextBlue = 0.024 } })
		elseif roundScore <= 20 then
			SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { ScaleTarget = 2.0, ScaleSpeed = 999.0, TextRed = 1.000, TextGreen = 0.329, TextBlue = 0.000 } })
		else
			SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { ScaleTarget = 2.6, ScaleSpeed = 999.0, TextRed = 1.000, TextGreen = 0.800, TextBlue = 0.000 } })
		end

		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { MoveTargetShiftY = -66 } })

		wait(0.01)

		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { ScaleTarget = 1.25, ScaleSpeed = 4.0 } })

		wait(0.6)

		for goalHealth = previousGoalHealth - 1, team.PyreHealth, -1 do
			goalHealthTicks = goalHealthTicks + 1
			SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthText", Properties = { SetText = goalHealth, FadeTarget = 1.0 } })
			if goalHealthTicks % 10 == 0 then
				PlaySound({ Name = "/SFX/Menu Sounds/ScoreIcon", Id = team.GoalButtonId })
			end
			wait(0.3 * (10/DEFAULT_PYRE_HEALTH) )
		end
		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { FadeTarget = 0.0 } })
	else
		-- Healing
		local healAmount = math.abs( previousGoalHealth - team.PyreHealth )
		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthText", Properties = { SetText = previousGoalHealth, FadeTarget = 1.0 } })
		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { ScaleTarget = 2.0, ScaleSpeed = 999.0, TextRed = 0.122, TextGreen = 0.753, TextBlue = 0.224 } })
		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { SetText = "+"..healAmount, FadeOpacity = 1.0, FadeTarget = 1.0 } })
		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { MoveTargetShiftY = -66 } })
		wait(0.01)
		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { ScaleTarget = 1.25, ScaleSpeed = 4.0 } })
		wait(0.6)
		local goalHealth = previousGoalHealth + 1
		while goalHealth <= team.PyreHealth do
			goalHealthTicks = goalHealthTicks + 1
			SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthText", Properties = { SetText = goalHealth, FadeTarget = 1.0 } })
			if goalHealthTicks % 10 == 0 then
				PlaySound({ Name = "/SFX/Menu Sounds/ScoreIcon", Id = team.GoalButtonId })
			end
			wait(0.03)
			goalHealth = goalHealth + 1
		end
		SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { FadeTarget = 0.0 } })
	end

	if specialCase == "PlayerIntro" then
		wait(2)
	end
	wait(1.0)
	thread( HideGoalHealth, team )
	SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthBacking", Properties = { AttachToObjectId = 0 }, Delay = 1.0 })
	--SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthIcon", Properties = { FadeTarget = 0.0 } })
	--SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthText", Properties = { FadeTarget = 0.0 } })

	goalHealthBar = false
	SetMenuOptions({ Name = "InGameUI", Properties = { Run = "AutoFadeGoalHealthOn" } })

	if specialCase ~= "RoundSwitch" then
		notifyExistingWaiters("goalHealthBarComplete")
	end

end

function HideGoalHealth( team, hideDelay )

	if hideDelay ~= nil and hideDelay > 0 then
		wait(hideDelay)
	end

	local goalComponent = nil
	if team == TeamB then
		goalComponent = "TeamBGoal"
		previousGoalHealth = TeamB.PrevPyreHealth
	else
		goalComponent = "TeamAGoal"
		previousGoalHealth = TeamA.PrevPyreHealth
	end

	SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthBacking", Properties = { FadeTarget = 0.0 } })
	SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthIcon", Properties = { Graphic = team.PyreIcon, FadeTarget = 0.0 } })
	--SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."DamageText", Properties = { AttachToObjectId = team.GoalButtonId } })
	SetMenuOptions({ Name = "InGameUI", Item = goalComponent.."HealthText", Properties = { SetText = team.PyreHealth, FadeTarget = 0.0 } })
end

function CheckMatchTermination()

	if TeamA.PyreHealth <= 0 or TeamB.PyreHealth <= 0 then
		if introMatch then
			thread( MarkObjectiveComplete, "WinMatchSuddenDeath" )
			if TeamA.PyreHealth >= TeamB.PyreHealth then
				thread(MarkObjectiveComplete, "WinMatchTutorial" )
			else
				-- don't mark off
			end
		else
			thread( MarkObjectiveComplete, "WinMatchGeneral" )
			if auraRageEventLive and TeamA.PyreHealth >= TeamB.PyreHealth then
				thread( MarkObjectiveComplete, "HandleAuraRage" )
			end
			if showdownActive and TeamA.PyreHealth >= TeamB.PyreHealth then
				thread( MarkObjectiveComplete, "WinShowdown" )
			end
			if practiceMatch and TeamA.PyreHealth >= TeamB.PyreHealth then
				thread(MarkObjectiveComplete, "FinishPracticeMatch")
			end
		end

		-- thread( PlayMatchSpeech, "reclaimorb" )
		MatchRecordEndControl( matchRecord, GetActiveUnitId({ PlayerIndex = 1}) )
		MatchRecordEndControl( matchRecord, GetActiveUnitId({ PlayerIndex = 2}) )
		MatchRecordEnd( matchRecord )
		matchRecord = nil

		if GetMapName({ }) ~= "MatchSiteB" then
			wait(0.8)
		end
		hideEntireUI()
		thread( HideObjectiveLog ) -- hide objective log for final pan
		thread( MatchEndPresentation )
	end

end

function MatchEndPresentation()

	matchEnd = true

	DisableInput({ PlayerIndex = 1 })
	DisableInput({ PlayerIndex = 2 })

	SetUnitProperty({ Name = "ContinuousWeapon", Value = "null", DestinationIds = TeamAObjectIds })
	SetUnitProperty({ Name = "CollideWithObstacles", Value = false, DestinationIds = TeamAObjectIds })

	SetUnitProperty({ Name = "ContinuousWeapon", Value = "null", DestinationIds = TeamBObjectIds })
	SetUnitProperty({ Name = "CollideWithObstacles", Value = false, DestinationIds = TeamBObjectIds })

	if introMatch then
		for k, v in pairs( TeamAObjectIds ) do
			RespawnUnit( v )
		end

		for k, v in pairs( TeamBObjectIds ) do
			RespawnUnit( v )
		end

		--VanishEveryone()
		--PutEveryoneAroundSigil()
		return
	end

end

function TeamUnitExit( unitObjectId )

	PlaySound({ Name = "/SFX/Match SFX/AuraOff", Id = unitObjectId })

	local unitName = GetName({ Id = unitObjectId })
	PlayAnimation({ Name = ArchetypeData[unitName].ExitAnimation, DestinationId = unitObjectId })

	SetColor({ Id = unitObjectId, Color = Color.Black, Duration = 0.3 })
	SetOpacity({ Id = unitObjectId, Fraction = 0, Duration = 0.35, Delay = 0.25 })
	Teleport({ Id = unitObjectId, UseCurrentLocation = true, OffsetX = -5000, Delay = 0.6 })
	AdjustZLocation({ Id = unitObjectId, Distance = 1000, Duration = 1 })
	SetScale({ Id = unitObjectId, Fraction = 2.0, Delay = 0.3, Duration = 0.35 })

end

function PostScore( scorerId )

	CheckMatchTermination()
	local scoringTeam = GetMatchTeamByCharacterId( scorerId )

	if TeamA.PyreHealth <= 0 or TeamB.PyreHealth <= 0 then
		-- used for LocalMP Victory
		local characterData = GetCharacterTableByObjectId( scorerId )
		TagWinningScoreCharacter( characterData )
		WinningScore()
		MatchVictory()
		return
	end

	-- Continue match to next round

	UseableOn({ Names = { "GoalAButtonGroup", "GoalBButtonGroup" } })

	stopRespawner = false

	someoneScored = false

	scoreSequence = true

	DisableInput({ PlayerIndex = 1 })
	DisableInput({ PlayerIndex = 2 })
	thread( ShowInputDisabledPresentation )

	PlaySound({ Name = "/SFX/Match SFX/MatchStart" })

	local victoryColor = Color.Black -- test to just make the fades black
	FadeOut({ Color = victoryColor, Duration = 0.35 })
	FocusCamera({ Fraction = 3, Duration = 0.8, ZoomType = "Overshoot" })

	wait(0.35)

	-- Things to happen under full black
	ResetPlayerRespawn()

	NextRound( scoringTeam )

	wait(0.15)

	FadeIn({ Duration = 0.3 })

	if snowing then
		AdjustFullscreenBloom({ Name = "RainBlurSlighter", Duration = 0.5 })
	end

	if SacrificeScorer ~= nil and CheckUnitSkillChance( SacrificeScorer, "SacrificeScoreRespawn" ) then
		SacrificeScorer.RespawnTimer = 2
	end

	if SacrificeScorer ~= nil and scoringTeam ~= PlayerTeam and ConstellationActive( "RivalTeamSacrificeRespawn" ) then
		SacrificeScorer.RespawnTimer = 2
	end

	if SacrificeScorer ~= nil then
		local flyingSkill = GetUnitSkillValue( SacrificeScorer, "UniqueFlyingSkill" )
		if flyingSkill > 0 and airScore and CoinFlip() then
			SacrificeScorer.RespawnTimer = 2
		end
	end

	local activeEvent = CheckMidMatchEvents()
	if activeEvent == nil then
		PlayMatchDialogue( "midmatch" )
	end

	-- for misc use
	FireTrigger({ Name = "StartedNextRound" }) -- currently only in MatchSiteH

	airScore = false

	if not blockMatchInputReEnable then

		thread( MeteoriteSlam, ballId, nil, 0.2, true )
		SetOpacity({ Id = ballId, Fraction = 1.0, Delay = 0.35, Duration = 0.1 })

		wait(0.85)

		if introMatch then
			wait(0.4)
		end

		if not midMatchEventLive then
			EnableInput({ PlayerIndex = 1 })
			EnableInput({ PlayerIndex = 2 })
			thread( HideInputDisabledPresentation )
		end
	end

	CheckPairPowerUp( TeamA )
	CheckPairPowerUp( TeamB )

	wait(1.5)

	SetVulnerable({ Ids = TeamAObjectIds })
	SetVulnerable({ Ids = TeamBObjectIds })
	SacrificeScorer = nil
	ThrowScorer = nil

	scoreSequence = false

	-- fire continuous weather events
	if windy then
		WindStorm()
	end

	thread(PickMatchObjective)

end

function MatchVictory()
	if not introMatch and not netMPMatch then
		CloseMatchConclusionPresentation()
	end
	wait(0.5)

	local fadeOutTime = 0.25
	StopSound({ Id = goalAFireAmbientLoopId, Duration = fadeOutTime })
	StopSound({ Id = goalBFireAmbientLoopId, Duration = fadeOutTime })

	if netMPMatch then
		FireTrigger({ Name = "PostMPMatchTrigger"})

	elseif IsMultiplayerMatch() then
		endScreenTextConstant = SpawnObstacle({ Name = "InvisibleTarget", DestinationId = finalCamTarget, Group = "MatchConclusion", OffsetY = -2000 })
		wait(0.3)
		PlayMatchDialogue( "postmatch" )
		FadeOutAllAmbience( 5 )
		if RequestLocalMPRematch then
			wait(0.5)
		else
			wait(2.0)
		end
		FadeOut({ Duration = 0.4, Color = Color.Black })
		wait(0.6)
		--SetConfigOption({ Name = "LocalMPReady", Value = true })
		if ConsecutiveLocalMPMatches == nil then
			ConsecutiveLocalMPMatches = 0
		end
		ConsecutiveLocalMPMatches = ConsecutiveLocalMPMatches + 1
		PersistVariable({ Name = "ConsecutiveLocalMPMatches" })
		if ArcadeMode then
			if TeamA.PyreHealth > TeamB.PyreHealth then
				RequestStartNextArcadeMatch()
			else
				-- Run Defeat
				ArcadeModeRunFinished()
			end
		elseif RequestLocalMPRematch then
			RestartMatch()
		else
			RequestStartLocalMP()
		end

	elseif introMatch then
		HideMatchUI()
		FadeOutAllAmbience( 5 )

		cameFromIntroMatch = true
		PersistVariable({ Name = "cameFromIntroMatch" })

		AdjustFullscreenBloom({ Name = "SaturatedLight", Duration = 2, Delay = 3 })
		StopSound({ Name = "/Ambience/MatchSiteAAmbience", Duration = 4.3 })
		wait(4.5)
		FadeOut({ Duration = 0.5, Color = Color.Black })
		wait(2.5)
		SaveCheckpoint({ StartNextMap = "Campaign", DevSaveName = CreateReturnDevSaveName() })
		LoadMap({ Name = "Campaign" })

	elseif practiceMatch then
		HideMatchUI()

		PlayMatchDialogue( "postmatch" )
		FadeOutAllAmbience( 5 )
		FadeOut({ Color = Color.Black, Duration = 0.4 })

		wait(1.45)
		-- count freeplay wins
		if ChallengeData == nil then
			if TeamA.PyreHealth > TeamB.PyreHealth then
				TeamA.HasWonFreePlayMatch = true
			end
		end
		LeavingPracticeMatch()
		SaveCheckpoint({ StartNextMap = "Campaign", DevSaveName = CreateReturnDevSaveName() })
		LoadMap({ Name = "Campaign" })

	elseif ascensionMatch then
		HideMatchUI()

		if TeamA.PyreHealth > TeamB.PyreHealth then
			AscensionSuccessPresentation()
		else
			AscensionFailurePresentation()
		end

		-- music ends in PostMatch
		-- FireTrigger({ Name = "EndCurrentMusic" })

		PlayMatchDialogue( "postmatch" )

		-- this is done in CommencePostMatch()
		-- StartNewWeek()
		StartNewSeason()

		FadeOut({ Color = Color.Black, Duration = 0.4 })
		UpdateDraftStatus()

		if PlayerTeam.AscensionMatchesPlayed < 7 then
			wait(0.45)
			CommencePostMatch()
		else
			FadeOutAllAmbience( 5 )
			FireTrigger({ Name = "EndCurrentMusic", Delay = 0.45 })
			if ascensionMatch then
				blockMatchVO = false
				blockMusicChanges = false
			end
			wait(0.5)
			--wait(1.45)
			--SaveCheckpoint({ StartNextMap = "Campaign", DevSaveName = CreateReturnDevSaveName() })
			LoadMap({ Name = "Epilogue" })
		end
	else
		-- post-match flow for most Campaign matches
		PlayMatchDialogue( "postmatch" )
		FadeOut({ Color = Color.Black, Duration = 0.4 })
		UpdateDraftStatus()
		wait(0.45)
		CommencePostMatch()
	end
end

function ClearDefenders()

	local tempUnits = GetIdsByType({ Name = "DefenderUnit" })
	if not IsEmpty( tempUnits ) then
		Destroy({ Ids = tempUnits, SuppressDeathWeapon = true })
	end
	local tempUnits2 = GetIdsByType({ Name = "DefenderUnitMini" })
	if not IsEmpty( tempUnits2 ) then
		Destroy({ Ids = tempUnits2, SuppressDeathWeapon = true })
	end

end

function NextRound( scoringTeam )

	local currentRoundMap = GetMapName({ })
	nextRoundSequence = true

	-- put everything back into position
	for k, v in pairs( StartingPositions ) do
		Teleport({ Id = k, DestinationId = v })
		if Contains(TeamAObjectIds, k) then
			SetGoalAngle({ Id = k, Angle = 0, CompleteAngle = true })
		else
			SetGoalAngle({ Id = k, Angle = 180, CompleteAngle = true })
			if noEnemies and k ~= ballId then
				SetOpacity({ Id = k, Fraction = 0, Duration = 0 })
			end
		end
	end

	ClearDefenders()
	if ResetRockStartPoints ~= nil then
		thread( ResetRockStartPoints )
	end
	if TerminateAuraLinkEventSilently ~= nil then
		TerminateAuraLinkEventSilently()
	end
	TauntSwappedThisRound = { }
	TauntStunnedThisRound = { }

	for k, v in pairs(ImpifiedUnits) do
		Deimpify(k)
	end

	Destroy({ Id = TeamA.BeaconExit })
	Destroy({ Id = TeamB.BeaconExit })

	-- match camera
	-- LockCamera({ Id = 40002, Duration = 0.1 })
	--AdjustZoom({ Fraction = 0.6, Duration = 9999, LerpTime = 0.1 })
	FocusCamera({ Fraction = 0.6, Duration = 0.1 })

	Teleport({ Id = ballId, DestinationId = StartingPositions[ballId] })
	PutDownGently({ Id = ballId })
	BallTossed = false
	UseableOn({ Id = ballId })
	PlayAnimation({ DestinationId = ballId, Name = "InkBallBuried" })
	SetOpacity({ Id = ballId, Fraction = 0 })

	if TeamHasSkillDrafted( TeamA, "LosingBallPosition" ) and TeamHasSkillDrafted( TeamB, "LosingBallPosition" ) then
		-- do nothing
	elseif TeamHasSkillDrafted( TeamA, "LosingBallPosition" ) and TeamA.PyreHealth < TeamB.PyreHealth then
		Teleport({ Id = ballId, DestinationId = PositionIds.LeftMiddleId, OffsetX = 300 })
	elseif TeamHasSkillDrafted( TeamB, "LosingBallPosition" ) and TeamB.PyreHealth < TeamA.PyreHealth then
		Teleport({ Id = ballId, DestinationId = PositionIds.RightMiddleId, OffsetX = -300 })
	end

	wait(0.125)

	LockCamera({ Id = ballId, Duration = 0 })
	SetCameraClamp({ Ids = { CameraBorderObjectIds[1], CameraBorderObjectIds[2] }, LerpTime = 0.01 })
	DynamicZoomCamera({ Enabled = true, Speed = 0.05 })
	blockFancyCamera = false
	thread( MatchCameraSelector, "newRound" )
	--thread(ShowGoalHealth, TeamA, "RoundSwitch")

	-- place ball differently
	Unattach({ Id = ballId, CenteGeometry = true })
	BallCarrierId = 0
	SetScale({  Id = ballId, Fraction = 1, Duration = 0.01 })

	if currentRoundMap == "MatchSiteI" then
		thread(ScribePillars)
	end

	if introMatch and TeamA.NumScores == 1 then
		DynamicZoomCamera({ Enabled = false, Speed = 0.01 })
		thread( PostFirstScoreTutorial )
	elseif introMatch and TeamA.NumScores == 2 and TeamB.NumScores == 0 then
		thread( ShowSacrificedUnitRespawn )
	elseif CheckAdditionalTutorialObjectives() then
		thread( ProcessTalliedActions )
	end

	if introMatch and ( TeamA.NumScores == 3 or TeamB.NumScores == 1 ) then
		-- thread( ShowControlsUITutorial )
	end

	if GetMapName({ }) == "MatchSiteA" and TeamA.NumScores >= 3 then
		if Contains( TeamAObjectIds, lastSacrificer ) then
			DebugPrint({ Text = "Intro Match Handicap Harder" })
			if TeamB.PyreHealth ~= nil then
				local pyreHealthFraction = TeamB.PyreHealth/TeamB.PyreMaxHealth
				local introHandicap = Lerp(3, 4, pyreHealthFraction)
				--SetConfigOption({ Name = "PlayerTwoAIHandicap", Value = 3.1 - (0.03 * (DEFAULT_PYRE_HEALTH - TeamB.PyreHealth)) })
				SetConfigOption({ Name = "PlayerTwoAIHandicap", Value = introHandicap })
			else
				SetConfigOption({ Name = "PlayerTwoAIHandicap", Value = 4.0 })
			end
		else
			DebugPrint({ Text = "Intro Match Handicap Easier" })
			SetConfigOption({ Name = "PlayerTwoAIHandicap", Value = 5 })

		end
	end

	-- Resume AI
	if TeamAIs[TeamB.LeagueIndex] ~= nil then
		thread( MatchAI, TeamAIs[TeamB.LeagueIndex] )
	else
		thread( MatchAIAdaptive )
	end

	SetConfigOption({ Name = "UseOcclusion", Value = true })

	temporarilyRemoveSacrificedScorer = false

	-- switch to middle or bottom unit
	local mapName = GetMapName({ })
	for k, v in pairs(TeamAObjectIds) do
		local unitData = GetCharacterTableByObjectId( v )
		if unitData ~= nil then
			if unitData.RespawnTimer < 0 and v == PositionIds.LeftMiddleId then
				SwitchActiveUnit({ Id = PositionIds.LeftMiddleId, PlayerIndex = 1 })
				break
			elseif unitData.RespawnTimer < 0 and v == PositionIds.LeftBottomId then
				SwitchActiveUnit({ Id = PositionIds.LeftBottomId, PlayerIndex = 1 })
				break
			end
		end
	end

	for k, v in pairs(TeamBObjectIds) do
		local unitData = GetCharacterTableByObjectId( v )
		if unitData ~= nil then
			if unitData.RespawnTimer < 0 and v == PositionIds.RightMiddleId then
				SwitchActiveUnit({ Id = PositionIds.RightMiddleId, PlayerIndex = 2 })
				break
			elseif unitData.RespawnTimer < 0 and v == PositionIds.RightBottomId then
				SwitchActiveUnit({ Id = PositionIds.RightBottomId, PlayerIndex = 2 })
				break
			end
		end
	end

	-- in intro match switch to Hedwyn or Rukey instead of Jodi
	local currentPostForceSwtichUnit = GetActiveUnitId({  })
	if introMatch and TeamA.NumScores == 1 and GetName({ Id = currentPostForceSwtichUnit }) == "PlayerLarge" then
		if lastScorer == 20001 then
			SwitchActiveUnit({ Id = 190001, PlayerIndex = 1, Delay = 0.5 })
		else
			SwitchActiveUnit({ Id = 20001, PlayerIndex = 1, Delay = 0.5 })
		end
	end

	if GetMapName({ }) == "MatchSiteMP" then
		TeamA.PyreHealth = TeamA.PyreMaxHealth
		TeamB.PyreHealth = TeamB.PyreMaxHealth
	end

	nextRoundSequence = false

	ObjectiveRoundReset( scoringTeam )

end

function PlaceAroundThePyre( winningBench )
	if winningBench == nil then
		winningBench = TeamAObjectIds
	end

	local UnitPyreOffsets =
	{ [1] = { x = -300, y = 300 },
	  [2] = { x = 0, y = 300 },
	  [3] = { x = 300, y = 300 },
	}

	DisarmAuras()
	local numKey = 1
	for k, v in pairs(winningBench) do
		--Teleport({ Id = v, DestinationId = goalA, OffsetX = UnitPyreOffsets[numKey][x], OffsetY = UnitPyreOffsets[numKey][y] })
		--AngleTowardTarget({ Id = v, DestinationId = goalA })

		AngleTowardTarget({ Id = v, DestinationId = goalA })

		local unitName = GetName({ Id = v })
		if unitName == "PlayerSmall" then
			PlayAnimation({ Name = "PlayerSmallCastChargingSlow", DestinationId = v })
		--	Teleport({ Id = v, DestinationId = goalA, OffsetX = 300, OffsetY = 300 })
		elseif unitName == "PlayerMedium" then
			PlayAnimation({ Name = "PlayerMediumCastCharging", DestinationId = v })
		--	Teleport({ Id = v, DestinationId = goalA, OffsetX = 0, OffsetY = 300 })
		elseif unitName == "PlayerLarge" then
			PlayAnimation({ Name = "PlayerLargeCastChargingSlow", DestinationId = v })
		--	Teleport({ Id = v, DestinationId = goalA, OffsetX = -300, OffsetY = 300 })
		end


		numKey = numKey + 1
	end
end

function SetStartingPositions( teamA, teamB )

	ClearCharacterObjectIds()

	StartingPositions = {  }

	StartingPositionDataTable = { } -- this is a new construction for now that I might eventually convert the whole thing to

	teamA.CameraLockPositions = {}
	for k, v in pairs( teamA.ObjectIds ) do

		local targetId = teamA.GoalButtonId
		local positionKey = GetKey( PositionIds, v )
		local positionOffsets = StartPositionOffsets[positionKey]
		if positionOffsets == nil then
			targetId = v
			positionOffsets = { OffsetX = 0, OffsetY = 0 }
		end
		local newStartingPosition = SpawnObstacle({ Name = "InvisibleTarget", Group = "KickoffHelpers", DestinationId = targetId, OffsetX = positionOffsets.OffsetX, OffsetY = positionOffsets.OffsetY })
		StartingPositions[v] = newStartingPosition
		Teleport({ Id = v, DestinationId = StartingPositions[v] })

		if v == 20001 then
			StartingPositionDataTable["Middle"] = newStartingPosition
		elseif v == 190000 then
			StartingPositionDataTable["Top"] = newStartingPosition
		elseif v == 190001 then
			StartingPositionDataTable["Bottom"] = newStartingPosition
		end

		local newCameraSpot = SpawnObstacle({ Name = "InvisibleTarget", Group = "PlayerCameraHelpers", DestinationId = v })
		teamA.CameraLockPositions[v] = newCameraSpot
		Attach({ Id = newCameraSpot, DestinationId = v, OffsetX = 600 })
		AddToGroup({ Name = "TeamACameraLockPoints", Id = newCameraSpot })
	end

	teamB.CameraLockPositions = {}
	for k, v in pairs( teamB.ObjectIds ) do

		local targetId = teamB.GoalButtonId
		local positionKey = GetKey( PositionIds, v )
		local positionOffsets = StartPositionOffsets[positionKey]
		if positionOffsets == nil then
			targetId = v
			positionOffsets = { OffsetX = 0, OffsetY = 0 }
		end
		local newStartingPosition = SpawnObstacle({ Name = "InvisibleTarget", Group = "KickoffHelpers", DestinationId = targetId, OffsetX = positionOffsets.OffsetX, OffsetY = positionOffsets.OffsetY })
		StartingPositions[v] = newStartingPosition
		Teleport({ Id = v, DestinationId = StartingPositions[v] })

		local newCameraSpot = SpawnObstacle({ Name = "InvisibleTarget", Group = "PlayerCameraHelpers", DestinationId = v })
		teamB.CameraLockPositions[v] = newCameraSpot
		Attach({ Id = newCameraSpot, DestinationId = v, OffsetX = -600 })
		AddToGroup({ Name = "TeamBCameraLockPoints", Id = newCameraSpot })
	end

	for k, v in pairs(GetIds({ Name = "Token" })) do
		local newStartingPosition = SpawnObstacle({ Name = "InvisibleTarget", Group = "KickoffHelpers", DestinationId = v })
		StartingPositions[v] = newStartingPosition

		local newCameraSpotA = SpawnObstacle({ Name = "InvisibleTarget", Group = "PlayerCameraHelpers", DestinationId = v })
		local newCameraSpotB = SpawnObstacle({ Name = "InvisibleTarget", Group = "PlayerCameraHelpers", DestinationId = v })
		Attach({ Id = newCameraSpotA, DestinationId = v, OffsetX = 300 })
		Attach({ Id = newCameraSpotB, DestinationId = v, OffsetX = -300 })
		AddToGroup({ Name = "BallACameraLockPoint", Id = newCameraSpotA })
		AddToGroup({ Name = "BallBCameraLockPoint", Id = newCameraSpotB })
	end

end

OnDestroyAny{ "TeamB",
	function(triggerArgs)
		-- VO for Power Shot Kills
		if triggerArgs.IsPerfectCharge then
			thread( PowerShotKillAudio )
		end
		TrackKills( triggerArgs.triggeredById, triggerArgs.KillerId, triggerArgs.name )
		DoKillCamAndUnitSwitch( triggerArgs.triggeredById, triggerArgs.KillerId, TeamB, triggerArgs.name, triggerArgs.IsPerfectCharge )
	end
}

OnDestroyAny{ "TeamA",
	function(triggerArgs)
		-- VO for Power Shot Kills
		if triggerArgs.IsPerfectCharge then
			thread( PowerShotKillAudio )
		end
		TrackKills( triggerArgs.triggeredById, triggerArgs.KillerId, triggerArgs.name )
		DoKillCamAndUnitSwitch( triggerArgs.triggeredById, triggerArgs.KillerId, TeamA, triggerArgs.name, triggerArgs.IsPerfectCharge )
	end
}

function DoKillCamAndUnitSwitch( deadUnit, killer, deadCharTeam, weapon, perfectKill )

	local activeUnitId = deadCharTeam.CurrentControlId
	local useLastKillCam = false
	local killerTeam = GetTeamByCharacterId( killer )
	if killerTeam ~= nil then
		if IsLastUnitStanding( deadUnit ) then
			PlaySound({ Name = "/SFX/Menu Sounds/TeamWipe" })
			useLastKillCam = true
		elseif killerTeam.CastNumKills >= 2 and killerTeam.CastKilledControlledUnit then
			useLastKillCam = true
		end
		thread( DoKillSlowCam, deadUnit, killer, deadCharTeam.PlayerIndex, weapon, useLastKillCam, perfectKill )
	end

	if deadUnit == activeUnitId then
		Rumble({ Fraction = 0.3, Duration = 0.3, PlayerIndex = deadCharTeam.PlayerIndex })
	end

	wait(0.4)

	if deadUnit == activeUnitId then
		ForceSwitchToSoonestRespawn( deadCharTeam )
	end

end

function ForceSwitchToSoonestRespawn( team )

	if fakeJumpPresentation or flightSchool or impInstitute then
		return
	end

	local activeUnitId = team.CurrentControlId
	local nearestLivingUnit = GetNearestLivingTeamMate( activeUnitId )
	if nearestLivingUnit ~= nil then
		SwitchActiveUnit({ Id = nearestLivingUnit, PlayerIndex = team.PlayerIndex })
		if playerIndex == 1 or netMPMatch then
			Flash({ Id = nearestLivingUnit, Speed = 2, MinFraction = 0, MaxFraction = 0.7, Color = Color.White, Delay = 0 })
			StopFlashing({ Id = nearestLivingUnit, Delay = 1.1 })
		end
		return
	end

	-- Switch to the next respawning unit (could even be the unit that just died)
	local lowestRespawnTimer = 99999
	local nextRespawnUnitId = nil
	local teamObjectIds = team.ObjectIds
	for k, v in pairs( teamObjectIds ) do
		local playerTable = GetCharacterTableByObjectId(v)
		if playerTable == nil then
			return
		end
		if playerTable.RespawnTimer < lowestRespawnTimer then
			lowestRespawnTimer = playerTable.RespawnTimer
			nextRespawnUnitId = v
		end
	end

	if nextRespawnUnitId ~= nil and nextRespawnUnitId ~= activeUnitId then
		SwitchActiveUnit({ Id = nextRespawnUnitId, PlayerIndex = team.PlayerIndex })
	end

end

function GetNearestLivingTeamMate( unitId )

	if unitId == nil then
		return
	end

	local lowestDistance = 9999
	local lowestUnitId = nil

	local character = CharacterCache[unitId]
	local team = League[character.TeamIndex]

	for k, teamMateId in pairs( team.ObjectIds ) do
		local teamMate = CharacterCache[teamMateId]
		if teamMate ~= nil and teamMate.RespawnTimer <= 0 and unitId ~= teamMateId then
			local distanceBetweenTargets = GetDistance({ Id = unitId, DestinationId = teamMateId })
			if distanceBetweenTargets > 0 and distanceBetweenTargets < lowestDistance then
				lowestDistance = distanceBetweenTargets
				lowestUnitId = teamMateId
			end
		end
	end

	nearestUnitDistanceForFancyCamera = lowestDistance
	return lowestUnitId
end

CloseUpCamHelpers = { }

function CloseUpCam( camTarget01, camtarget02 )
	if fancyCamera or closeUp then
		blockFancyCamera = true
	end

	closeUp = true

	Delete({ Ids = CloseUpCamHelpers })
	CloseUpCamHelpers = { }

	HideMatchUI()

	local camTarget = GetActiveUnitId({ PlayerIndex = 1 })
	if camTarget01 ~= nil then
		camTarget = camTarget01
	end

	local camDuration = 1.5

	local camAnchor = SpawnObstacle({ Name = "InvisibleTarget", DestinationId = camTarget, Group = "CloseCamScripts" })

	local backgroundBlack = SpawnObstacle({ Name = "Auden_Blank_01", DestinationId = camAnchor, Group = "CloseCamScripts" })
	--local backgroundParchment = SpawnObstacle({ Name = "MapBG01", DestinationId = camAnchor, Group = "CloseCamScripts" })
	local backgroundGlow = SpawnObstacle({ Name = "HighriseGlow01", DestinationId = camAnchor, Group = "CloseCamScripts" })
	table.insert(CloseUpCamHelpers, backgroundBlack)
	table.insert(CloseUpCamHelpers, backgroundParchment)
	table.insert(CloseUpCamHelpers, backgroundGlow)

	SetColor({ Id = backgroundBlack, Color = { 0, 0, 0, 1 } })
	SetScale({ Id = backgroundBlack, Fraction = 50, Duration = 0 })
	SetOpacity({ Id = backgroundBlack, Fraction = 0.0, Duration = 0 })
	SetOpacity({ Id = backgroundBlack, Fraction = 1.0, Duration = 0.1, Delay = 0.05 })

	SetScale({ Id = backgroundParchment, Fraction = 3.5, Duration = 0 })
	SetOpacity({ Id = backgroundParchment, Fraction = 0.0, Duration = 0 })
	SetOpacity({ Id = backgroundParchment, Fraction = 0.2, Duration = 0.1, Delay = 0.05 })

	SetColor({ Id = backgroundGlow, Color = { 0, 208, 255, 255 } })
	SetScale({ Id = backgroundGlow, Fraction = 10, Duration = 0 })
	SetOpacity({ Id = backgroundGlow, Fraction = 0.0, Duration = 0 })
	SetOpacity({ Id = backgroundGlow, Fraction = 0.4, Duration = 0.1, Delay = 0.1 })

	--AdjustFrame({ Fraction = 0.235, R = 1, G = 1, B = 1, A = 1.0, Duration = 0.25, Delay = 0.05 })

	local matchBloom = GetBloomSettingName({ })
	AdjustFullscreenBloom({ Name = "NewType07", Duration = 0.1  })

	RemoveFromGroup({ Id = camTarget, Name = "Art_Standing01" })
	AddToGroup({ Id = camTarget, Name = "Events", DrawGroup = true })

	if camtarget02 ~= nil then
		RemoveFromGroup({ Id = camtarget02, Name = "Art_Standing01" })
		AddToGroup({ Id = camTarget02, Name = "Events", DrawGroup = true })
	end

	PlaySound({ Name = "/SFX/World Sounds/ChoirZoomOut" })

	InsertGroupInFront({ Name = "CloseCamScripts", DestinationName = "Celebration" })
	InsertGroupInFront({ Name = "CloseCamScriptFeature", DestinationName = "CloseCamScripts" })

	--PanCamera({ Id = camAnchor, Duration = 0.3, EaseIn = 0, EaseOut = 0.2, OffsetX = 500 })
	--PanCamera({ Id = camAnchor, Duration = 1.3, EaseIn = 2, EaseOut = 0.2, OffsetX = 400, OffsetY = 70, Delay = 0.1, Retarget = true })
	FocusCamera({ Fraction = 0.8, Duration = 0.3, ZoomType = "Overshoot" })

	ShakeScreen({ Distance = 2, Speed = 200, FalloffSpeed = 2000, Duration = 0.9, Angle = 0 })

	wait(camDuration)

	SetOpacity({ Ids = CloseUpCamHelpers, Fraction = 0, Duration = 0.2 })
	--AdjustFrame({ Fraction = 0, R = 0, G = 0, B = 0, A = 0.0, Duration = 0.2, Delay = 0 })

	RemoveFromGroup({ Id = camTarget, Name = "Events" })
	AddToGroup({ Id = camTarget, Name = "Art_Standing01", DrawGroup = true })

	if camtarget02 ~= nil then
		RemoveFromGroup({ Id = camtarget02, Name = "Events" })
		AddToGroup({ Id = camtarget02, Name = "Art_Standing01", DrawGroup = true })
	end

	AdjustFullscreenBloom({ Name = matchBloom, Duration = 0.2  })

	closeUp = false

	blockFancyCamera = false
	thread( MatchCameraSelector, "fancyPostScore" )
	--ShowMatchUI()

end

MatchAsideObjects = { }

function MatchAside( camTargets )

	if camTargets == nil then
		return
	end

	if fancyCamera or closeUp then
		blockFancyCamera = true
	end

	AdjustSimulationSpeed({ Fraction = 1.0, LerpTime = 0.00001 })

	-- audio FX
	SetSoundCueValue({ Id = GetMixingId({}), Names = { "LowPass" }, Value = 1.0, Duration = 0.5 })

	Delete({ Ids = CloseUpCamHelpers })
	CloseUpCamHelpers = { }

	matchAside = true
	HideMatchUI()

	SetMenuOptions({ Name = "InGameUI", Item = "BallHeightLine", Properties = { FadeTarget = 0.0 } })

	if allUnitsDeadPresentation and normalMatchBloom ~= nil then
		AdjustFullscreenBloom({ Name = normalMatchBloom, Duration = 0.1 })
	end

	if allUnitsDeadPresentation then
		thread(ResumeDefaultMatchColorGrade, 0.15 )
	end
	--table.insert(camTargets, lastSacrificer)

	local unitData = GetCharacterTableByObjectId( scorer )
	local camTarget = scorer
	local camTeam = League[unitData.TeamIndex]
	local camOpposingTeam = GetOpposingTeam( camTeam )
	local camDuration = 2
	finalCameraTarget = camTarget

	-- Jen bookmark scoring beauty
	--if not finalScore then
		matchAsideBackgroundBlack = SpawnObstacle({ Name = "Auden_Blank_01", DestinationId = lastGoalId, Group = "CloseCamScripts" })

		local matchAsideSun01 = SpawnObstacle({ Name = "Sun01", DestinationId = lastGoalId, Group = "CloseCamScriptFeature", OffsetX = 0, OffsetY = -100 })
		SetScale({ Id = matchAsideSun01, Fraction = 5.5, Duration = 0 })
		SetColor({ Id = matchAsideSun01, Color = { 21, 25, 36, 50 } })

		PlayOverheadAnimation({ Name = "SparkleFlakesScore", Group = "CloseCamScriptFeature", DestinationId = lastGoalId, IgnoreOverheadOffset = true, OffsetY = 0})

		--matchAsideBackgroundGlow = SpawnObstacle({ Name = "HighriseGlow01", DestinationId = lastGoalId, Group = "CloseCamScriptFeature", OffsetY = "100" })

		matchAsideGrain = SpawnObstacle({ Name = "Grain01", DestinationId = lastGoalId, Group = "CloseCamScriptFeature", OffsetX = 100, OffsetY = -200 })
		SetScale({ Id = matchAsideGrain, Fraction = 1.5, Duration = 0.1 })
		SetColor({ Id = matchAsideGrain, Color = {186, 97, 255, 200} })

		table.insert(CloseUpCamHelpers, matchAsideBackgroundBlack)
		table.insert(CloseUpCamHelpers, matchAsideSun01)
		table.insert(CloseUpCamHelpers, matchAsideSun02)
		table.insert(CloseUpCamHelpers, matchAsideGrain)

		--matchAsideVisionBlocker = SpawnObstacle({ Name = "TestVisionBlocker", DestinationId = lastGoalId, Group = "CloseCamScripts", OffsetX = 100, OffsetY = -200 })
		SetScale({ Id = matchAsideVisionBlocker, Fraction = 3 })
		table.insert(CloseUpCamHelpers, matchAsideVisionBlocker)

		matchAsideFacets = SpawnObstacle({ Name = "Facets", DestinationId = lastGoalId, Group = "MatchAside", OffsetY = -200 })
		SetScale({ Id = matchAsideFacets, Fraction = 3.54 })
		SetColor({ Id = matchAsideFacets, Color = {186, 97, 255, 10} })

		--matchAsideTorch01 = SpawnObstacle({ Name = "Torch", DestinationId = lastGoalId, Group = "MatchAside"})
		SetScale({ Id = matchAsideTorch01, Fraction = 20.5, Duration = 0 })
		SetColor({ Id = matchAsideTorch01, Color = {255, 255, 255, 255} })

		SetColor({ Id = matchAsideBackgroundBlack, Color = { 0, 0, 0, 255 } })
		SetColor({ Id = matchAsideBackgroundBlack, Color = { 21, 25, 36, 255 }, Duration = 1 })
		SetScale({ Id = matchAsideBackgroundBlack, Fraction = 50, Duration = 0 })

		SetColor({ Id = matchAsideBackgroundGlow, Color = { 0, 0, 0, 255 } })
		SetColor({ Id = matchAsideBackgroundGlow, Color = { 21, 25, 36, 255 }, Duration = 1 })
		SetScale({ Id = matchAsideBackgroundGlow, Fraction = 8, Duration = 0 })


		table.insert(CloseUpCamHelpers, matchAsideFacets)
		table.insert(CloseUpCamHelpers, matchAsideTorch01)
		table.insert(CloseUpCamHelpers, matchAsideTorch02)

	--end

	local offsetYModifier = VaporizeGraphics[unitData.Archetype]["TeleportOffsetY"]
	if not thrownScore and not forfeitMatch then
		Teleport({ Id = camTarget, DestinationId = camOpposingTeam.PyreId, OffsetY = -120 + offsetYModifier })
	end

	ClearCameraClamp({ LerpTime = 0.1 })
	local panOffsetX = 50
	if camTeam == TeamB then
		panOffsetX = -50
	end
	PanCamera({ Id = camOpposingTeam.GoalButtonId, Duration = 0.7, EaseIn = 0.05, EaseOut = 0.2, OffsetX = 0, OffsetY = -200, Delay = 0, Retarget = true })

	if thrownScore then
		FocusCamera({ Fraction = 0.5, Duration = camDuration, ZoomType = "Ease" })
	end

	--AdjustFrame({ Fraction = 0.235, R = 1, G = 1, B = 1, A = 1.0, Duration = 0.25, Delay = 0.05 })

	DisarmAuras()

	if thrownScore then
		--PlayOverheadAnimation({ Name = "ClickPoofOverlay", Scale = 1, DestinationId = ballId })
		--local thrownScoreAnimation = unitData.Archetype.."ThrowScore"
		--PlayAnimation({ Name = thrownScoreAnimation, DestinationId = camTarget })
		SetOpacity({ Id = camTarget, Fraction = 0, Duration = 0.25, Delay = 0.7 })
		unitOpacity = true
	end

	local matchBloom = GetBloomSettingName({ })
	AdjustFullscreenBloom({ Name = "SoftLight", Duration = 0.4  })

	local prevDrawGroups = {}

	-- Fire - On top
	for k, v in pairs( camTargets ) do
		local drawGroupName = GetGroupName({ Id = v, DrawGroup = true })
		prevDrawGroups[v] = drawGroupName
		RemoveFromGroup({ Id = v, Name = drawGroupName })
		AddToGroup({ Id = v, Name = "Events", DrawGroup = true })
	end

	-- Other goal objects - on the terrain
	local goalObjectIds = GetIds({ Name = "GoalB" })
	local goalGroup = "GoalB"
	if Contains(TeamBObjectIds, scorer) then
		goalObjectIds = GetIds({ Name = "GoalA" })
		goalGroup = "GoalA"
		thread(FlashLightBar, 1, TeamA.MaskHueRGB, 3, 10 )
	else
		thread(FlashLightBar, 2, TeamB.MaskHueRGB, 3, 10 )
	end

	for k, v in pairs( goalObjectIds ) do
		local drawGroupName = GetGroupName({ Id = v, DrawGroup = true })
		prevDrawGroups[v] = drawGroupName
		RemoveFromGroup({ Id = v, Name = drawGroupName })
		AddToGroup({ Id = v, Name = "Events_Ground", DrawGroup = true })
		InsertGroupBehind({ Name = "Events_Ground", DestinationName = "Events" })
	end

	local drawGroupName = GetGroupName({ Id = camTarget, DrawGroup = true })
	prevDrawGroups[camTarget] = drawGroupName
	RemoveFromGroup({ Id = camTarget, Name = drawGroupName })
	AddToGroup({ Id = camTarget, Name = "MatchAside", DrawGroup = true })

	InsertGroupInFront({ Name = "CloseCamScripts", DestinationName = "Celebration" })
	InsertGroupInFront({ Name = "CloseCamScriptFeature", DestinationName = "CloseCamScripts" })

	FocusCamera({ Fraction = 0.8, Duration = camDuration * 0.8, ZoomType = "Overshoot" })

	Shake({ Id = camTarget, Speed = 100, Distance = 1, Duration = camDuration })

	if finalScore and not introMatch then
		wait(1.1)
		SetOpacity({ Ids = { matchAsideBackgroundBlack, matchAsideBackgroundGlow }, Fraction = 0, Duration = 0.4, Delay = 1.75 })
		SetOpacity({ Ids = CloseUpCamHelpers, Fraction = 0, Duration = 0.4, Delay = 1.75 })
		SetOpacity({ Names = { "CloseCamScripts", "CloseCamScriptFeature" }, Fraction = 0, Duration = 0.4, Delay = 1.75 })
		DisarmAuras()
		SetOpacity({ Ids = TeamAObjectIds, Fraction = 0, Duration = 0.3, Delay = 1.3 })
		SetOpacity({ Ids = TeamBObjectIds, Fraction = 0, Duration = 0.3, Delay = 1.3 })

		Teleport({ Ids = TeamAObjectIds, DestinationId = ballId, OffsetY = 3000, Delay = 1.3 })
		Teleport({ Ids = TeamBObjectIds, DestinationId = ballId, OffsetY = 3000, Delay = 1.3 })

		thread(MatchFinalScoreBackground, 1.6 )

		--local nightSkyGroups = GetGroupWithSubGroups({ Name = "Art_NightSky01", Delay = 0 })
		--InsertGroupInFront({ Names = nightSkyGroups, DestinationName = "Celebration", Delay = 0 })
		--Teleport({ Names = nightSkyGroups, DestinationId = camTarget, KeepRelativeOffsets = true, OffsetY = -3000 })

		finalCamTarget = camOpposingTeam.PyreId
		PanCamera({ Id = camOpposingTeam.PyreId, Duration = 5.5, EaseIn = 0, EaseOut = 3, Retarget = true, OffsetY = -2000, Delay = 0.6 })
		--PanCamera({ Id = camOpposingTeam.PyreId, Duration = 5.5, EaseIn = 0, EaseOut = 3, Retarget = true, OffsetY = -500, Delay = 0.6 })
		local currentAsideZoom = GetCameraZoom({ })
		FocusCamera({ Fraction = currentAsideZoom * 0.5, Duration = 3, ZoomType = "Ease", Delay = 0.6 })
	end

	local currentGoalZoom = GetCameraZoom({ })

	if scorePresentation then
		if introMatch then
			waitUntil("scorePresentationComplete")
			wait(0.3)
		elseif not finalScore then
			waitUntil("scorePresentationComplete")
			wait(0.3)
		end
	elseif not finalScore then
		wait(camDuration)
	end

	if finalScore and not introMatch then
		wait(3.5)
	elseif finalScore and introMatch then -- at the end of the intromatch, keep everyone faded out
		SetOpacity({ Ids = TeamAObjectIds, Fraction = 0, Duration = 0 })
		SetOpacity({ Ids = TeamBObjectIds, Fraction = 0, Duration = 0 })

		SetPlayerUnitProperty({ DestinationIds = TeamBObjectIds, Name = "PhantomOutlineWidth", Value = 0 })

		-- set top and bottom gradient bounds
		SetPlayerUnitProperty({ DestinationIds = TeamBObjectIds, Name = "PhantomGradientTop", Value = 0 })
		SetPlayerUnitProperty({ DestinationIds = TeamBObjectIds, Name = "PhantomGradientBottom", Value = 0 })

		Teleport({ Ids = TeamAObjectIds, DestinationId = ballId, OffsetY = 3000 })
		Teleport({ Ids = TeamBObjectIds, DestinationId = ballId, OffsetY = 3000 })
	end

	--SetOpacity({ Id = camTarget, Fraction = 0, Duration = 0.2 })
	if not finalScore or introMatch then

		RemoveMatchAsideObjects()

		for k, v in pairs( camTargets ) do
			local drawGroupName = GetGroupName({ Id = v, DrawGroup = true })
			RemoveFromGroup({ Id = v, Name = drawGroupName })
			AddToGroup({ Id = v, Name = prevDrawGroups[v], DrawGroup = true })
		end

		for k, v in pairs( goalObjectIds ) do
			local drawGroupName = GetGroupName({ Id = v, DrawGroup = true })
			RemoveFromGroup({ Id = v, Name = drawGroupName })
			AddToGroup({ Id = v, Name = prevDrawGroups[v], DrawGroup = true })
		end

		RemoveFromGroup({ Id = camTarget, Name = "MatchAside" })
		AddToGroup({ Id = camTarget, Name = prevDrawGroups[camTarget], DrawGroup = true })

		AdjustFullscreenBloom({ Name = matchBloom, Duration = 0.2 })

		if finalScore and introMatch then
			-- do nothing
		else
			thread( ReArmAuras, 0.05 )
		end

		if unitOpacity then
			if finalScore and introMatch then
				-- do nothing for the final score
			else
				unitOpacity = false
				PlayAnimation({ Name = unitData.Archetype.."Idle", DestinationId = camTarget, Delay = 0 })
				SetOpacity({ Id = camTarget, Fraction = 1, Duration = 0.2, Delay = 0 })
			end
		end
	end

	-- audio FX / SFX filter / SFX ducking
	SetSoundCueValue({ Id = GetMixingId({}), Names = { "LowPass" }, Value = 0.0, Duration = 0.5 })

	SetMenuOptions({ Name = "InGameUI", Item = "BallHeightLine", Properties = { FadeTarget = 1.0 } })

	matchAside = false
	notifyExistingWaiters("matchAsideComplete")

end

function RemoveMatchAsideObjects()
	SetOpacity({ Ids = CloseUpCamHelpers, Fraction = 0, Duration = 0.2 })
	AdjustFrame({ Fraction = 0, R = 0, G = 0, B = 0, A = 0.0, Duration = 0.2, Delay = 0 })
end

function MatchFinalScoreBackground( asideDelay )
	local map = GetMapName({ })
	if map == "MatchSiteA" then
		return
	end
	thread(PresentNightSky, "FinalScore", asideDelay, map )
	wait(2.0)
	if map ~= "MatchSiteI" then
		-- SetGroupVisibility({ Name = "Art_Standing01", Visible = false }) -- potential optimization for final sequence
	end
end

function DisarmAuras()
	for k, v in pairs(TeamAObjectIds) do
		local equippedAura = GetUnitDataStringValue({ Id = v, Property = "ContinuousWeapon" })
		SetWeaponProperty({ DestinationId = v, WeaponName = equippedAura, PropertyName = "Enabled", Value = false  })
	end

	for k, v in pairs(TeamBObjectIds) do
		local equippedAura = GetUnitDataStringValue({ Id = v, Property = "ContinuousWeapon" })
		SetWeaponProperty({ DestinationId = v, WeaponName = equippedAura, PropertyName = "Enabled", Value = false  })
	end
end

function DisarmTeamAura( teamObjectIds )
	for k, v in pairs(teamObjectIds) do
		local equippedAura = GetUnitDataStringValue({ Id = v, Property = "ContinuousWeapon" })
		SetWeaponProperty({ DestinationId = v, WeaponName = equippedAura, PropertyName = "Enabled", Value = false  })
	end
end

function ReArmTeamAura( teamObjectIds )
	for k, v in pairs(teamObjectIds) do
		local equippedAura = GetUnitDataStringValue({ Id = v, Property = "ContinuousWeapon" })
		SetWeaponProperty({ DestinationId = v, WeaponName = equippedAura, PropertyName = "Enabled", Value = true  })
	end
end

function ReArmUnitAura( unitId )
	local equippedAura = GetUnitDataStringValue({ Id = unitId, Property = "ContinuousWeapon" })
	SetWeaponProperty({ DestinationId = unitId, WeaponName = equippedAura, PropertyName = "Enabled", Value = true  })
end

function EarlyAuraArm( rearmDelay )
	if rearmDelay ~= nil then
		wait(rearmDelay)
	end

	for k, v in pairs(TeamAObjectIds) do
		local equippedAura = GetUnitDataStringValue({ Id = v, Property = "ContinuousWeapon" })
		SetWeaponProperty({ DestinationId = v, WeaponName = equippedAura, PropertyName = "Enabled", Value = false  })
	end

	for k, v in pairs(TeamBObjectIds) do
		local equippedAura = GetUnitDataStringValue({ Id = v, Property = "ContinuousWeapon" })
		SetWeaponProperty({ DestinationId = v, WeaponName = equippedAura, PropertyName = "Enabled", Value = false  })
	end
end

function ReArmAuras( rearmDelay )
	if rearmDelay ~= nil then
		wait(rearmDelay)
	end

	if matchEnd then
		return
	end

	if noEnemies then -- for the intro match
		return
	end

	-- re-arm
	for k, v in pairs( TeamAObjectIds ) do
		local equippedAura = GetUnitDataStringValue({ Id = v, Property = "ContinuousWeapon" })
		SetWeaponProperty({ DestinationId = v, WeaponName = equippedAura, PropertyName = "Enabled", Value = true  })
	end

	for k, v in pairs( TeamBObjectIds ) do
		local equippedAura = GetUnitDataStringValue({ Id = v, Property = "ContinuousWeapon" })
		SetWeaponProperty({ DestinationId = v, WeaponName = equippedAura, PropertyName = "Enabled", Value = true  })
	end
end

function CombatInfoPanelCam( infoPanelDuration )

	if infoCamActive then
		return
	end

	if infoPanelDuration == nil then
		infoPanelDuration = 0.1
	end

	infoCamDurationModifier = (infoPanelDuration / 8)

	infoCamActive = true

	AdjustRadialBlurStrength({ Fraction = 1.25, Duration = 0.7 })
	AdjustRadialBlurDistance({ Fraction = 1, Duration = 0.7 })

	local matchBloom = GetBloomSettingName({ })
	-- BlurryZoom, SaturatedLight
	AdjustFullscreenBloom({ Name = "BlurryZoom", Duration = 0.95 })

	--AdjustSimulationSpeed({ Fraction = 0.2, LerpTime = 0.1 })

	wait(0.01)
	--AdjustSimulationSpeed({ Fraction = 0.5, LerpTime = 0.5 })

	wait(0.625 + infoCamDurationModifier)
	--AdjustSimulationSpeed({ Fraction = 1, LerpTime = 0.1 })

	wait(0.6)
	AdjustRadialBlurStrength({ Fraction = 0, Duration = 0.2 })
	AdjustRadialBlurDistance({ Fraction = 0, Duration = 0.2 })
	AdjustFullscreenBloom({ Name = matchBloom, Duration = 0.3 })

	infoCamActive = false

end

function BonfireKill( bonfireVictim, bonfireGoal )

	-- don't kill anyone before the match starts
	if not matchCommenced then
		return
	end

	-- don't kill if you don't have the ball
	if BallCarrierId ~= bonfireVictim then
		return
	end

	-- don't kill anyone after scoring has happened
	if stopRespawner then
		return
	end

	local bonfireKillColor = nil
	if bonfireGoal == goalA then
		bonfireKillColor = TeamA.MaskHueRGB
		bonfireKillFlame = { TeamA.PyreId }
	else
		bonfireKillColor = TeamB.MaskHueRGB
		bonfireKillFlame = { TeamB.PyreId }
	end

	if introMatch and not firstSacrificeScore then
		firstSacrificeScore = true
		local firstScorer = CharacterCache[bonfireVictim]
		thread( PlayIntroMatchSpeech, firstScorer.FirstScoreSpeech )
	end

	if not sacrificeScore then
		FireWeapon({ Name = "RespawnPushSky", DestinationId = bonfireGoal })
	elseif sacrificeScore then
		lastSacrificer = bonfireVictim

		blockFancyCamera = true
		temporarilyRemoveSacrificedScorer = true
		thread( SacrificeScoreDestroy, bonfireVictim )

		if TeamA.ObjectIdsLookup[bonfireVictim] then
			lastGoalId = goalB
			if introMatch then
				if MatchObjectives["GoalsTutorial"].Status == "Active" then
					thread( MarkObjectiveComplete, "GoalsTutorial" )
				end
				if jumpGoal then
					jumpGoal = false
					fakeJumpPresentation = false
					ToggleControl({ Names = { "Pass", "Cast", "AutoLock", "Sprint"}, Enabled = true })
					thread( PlayIntroMatchSpeech, "secondscore" )
					thread( MarkObjectiveComplete, "JumpTutorialGoal" )

					-- Restore default Grip
					for k, character in pairs( TeamB.AssignedCharacters ) do
						if character.Archetype == "PlayerSmall" then
							SetThingProperty({ Name = "Grip", Value = 850, DestinationId = character.ObjectId })
						elseif character.Archetype == "PlayerMedium" then
							SetThingProperty({ Name = "Grip", Value = 1000, DestinationId = character.ObjectId })
						elseif character.Archetype == "PlayerLarge" then
							SetThingProperty({ Name = "Grip", Value = 2000, DestinationId = character.ObjectId })
						end
					end

				end
			end
			CheckGoal( TeamA, TeamB )
		elseif TeamB.ObjectIdsLookup[bonfireVictim] then
			lastGoalId = goalA
			CheckGoal( TeamB, TeamA )
		end

	end
end

VaporizeGraphics =
{
	["PlayerSmall"] = { ScoreGraphic = "PlayerSmallScorePose", OpacityDelay = 2.6, TeleportOffsetX = 0, TeleportOffsetY = 100 },
	["PlayerMedium"] = { ScoreGraphic = "PlayerMediumScorePose", OpacityDelay = 2.4, TeleportOffsetX = 0, TeleportOffsetY = 0 },
	["PlayerMediumAlt"] = { ScoreGraphic = "PlayerMediumAltScorePose", OpacityDelay = 2.4, TeleportOffsetX = 0, TeleportOffsetY = 0 },
	["PlayerLarge"] = { ScoreGraphic = "PlayerLargeScorePose", OpacityDelay = 2.4, TeleportOffsetX = 0, TeleportOffsetY = -100 },
	["PlayerImp"] = { ScoreGraphic = "PlayerImpScorePose", OpacityDelay = 2.6, TeleportOffsetX = 0, TeleportOffsetY = 0 },

	["PlayerTrail"] = { ScoreGraphic = "PlayerTrailScorePose", OpacityDelay = 2.4, TeleportOffsetX = 0, TeleportOffsetY = 0 },
	["PlayerFlying"] = { ScoreGraphic = "PlayerFlyingScorePose", OpacityDelay = 2.6, TeleportOffsetX = 0, TeleportOffsetY = 0 },
	["PlayerTree"] = { ScoreGraphic = "PlayerTreeScorePose", OpacityDelay = 2.4, TeleportOffsetX = 0, TeleportOffsetY = 0 },
	["PlayerMonster"] = { ScoreGraphic = "PlayerMonsterScorePose", OpacityDelay = 2.4, TeleportOffsetX = 0, TeleportOffsetY = 0 },
}

function SacrificeScoreDestroy( unitId )
	local unitData = CharacterCache[unitId]
	unitData.RespawnTimer = InfiniteRespawnTime

	local unitDeathGraphic = GetLifeDataValue({ Property = "DeathGraphic", Id = unitId })
	local unitDeathSound = GetLifeDataValue({ Property = "DeathSound", Id = unitId })
	local unitTouchdownGraphic = GetThingDataValue({ Property = "TouchdownGraphic", Id = unitId })
	local vaporizeData = VaporizeGraphics[unitData.Archetype]

	local deathGraphic = vaporizeData.ScoreGraphic
	if unitData.SpecialScorePose then
		deathGraphic = unitData.SpecialScorePose
	end

	if forfeitMatch then
		--PlayAnimation({ Name = ArchetypeData[unitData.Archetype].TauntAnimation, DestinationId = unitId })
		SetOpacity({ Id = unitId, Fraction = 0, Delay = 0.6, Duration = 0.3 })
		return
	end

	SetLifeProperty({ Name = "DeathGraphic", Value = deathGraphic, DestinationId = unitId })
	SetLifeProperty({ Name = "DeathSound", Value = "null", DestinationId = unitId })
	SetThingProperty({ Name = "TouchdownGraphic", Value = "null", DestinationId = unitId })
	SetUnitProperty({ Name = "Draggable", Value = false, DestinationId = unitId })
	SetOpacity({ Id = unitId, Fraction = 0, Delay = vaporizeData.OpacityDelay, Duration = 0.25 })
	SetPlayerUnitProperty({ DestinationId = unitId, Name = "PhantomOutlineWidth", Value = 0, Delay = vaporizeData.OpacityDelay + 0.25 })
	Rumble({ Fraction = 0.4, Duration = 0.35, Delay = vaporizeData.OpacityDelay - 0.2 })
	thread( PyreScoreFx, "OnlyFlameVisuals", vaporizeData.OpacityDelay - 0.1 )

	Halt({ Id = unitId })
	PutDownGently({ Id = unitId })
	BallTossed = false
	Destroy({ Id = unitId, Delay = 0.3 })

	AllRumble({ Fraction = 0.2, Duration = 0.3 })

	AdjustColorGrading({ Name = "Scoring", Duration = 0, Delay = vaporizeData.OpacityDelay - 0.1 })
	thread( ResumeDefaultMatchColorGrade, 0.5, vaporizeData.OpacityDelay + 0.1 )

	wait(0.3)
	AllRumble({ Fraction = 0.4, Duration = 0.4 })

	wait(0.1)
	SetLifeProperty({ Name = "DeathGraphic", Value = unitDeathGraphic, DestinationId = unitId })
	SetLifeProperty({ Name = "DeathSound", Value = unitDeathSound, DestinationId = unitId })
	SetUnitProperty({ Name = "Draggable", Value = true, DestinationId = unitId })

	wait(0.3)
	AllRumble({ Fraction = 0.165, Duration = 0.4 })
	SetThingProperty({ Name = "TouchdownGraphic", Value = unitTouchdownGraphic, DestinationId = unitId })
end

function FindSkillSource( unitId, message )

	local character = GetCharacterTableByObjectId( unitId )
	if character == nil then
		return nil
	end

	local sourceInfo = { }
	local messageLength = string.len( message )
	for k, skillName in pairs( character.Skills ) do
		local skillPrefix = string.sub( skillName, 1, messageLength )
		if skillPrefix == message and HasSkillNotFromItem( character, skillName ) then
			-- Found a skill matching this message
			local skillData = PlayerSkillsTable[skillName]
			if skillData ~= nil then
				sourceInfo.Text = skillName
				sourceInfo.Image = skillData.PickedIcon
				return sourceInfo
			end
		end
	end

	local item = character.UpgradeItem
	if item ~= nil and item.SkillName == message then
		sourceInfo.Text = item.ItemName
		sourceInfo.Image = item.Icon
		return sourceInfo
	end

	return sourceInfo
end

local skillFeedbackQueue = {}

function SkillProcFeedback( unitId, message, texturePath, value )

	if someoneScored then
		wait( 0.01 )
	end

	local skillProcInfo = {}
	if SkillProcData[message] ~= nil then
		skillProcInfo.AppendText = SkillProcData[message].AppendText
		skillProcInfo.SFX = SkillProcData[message].SFX
	end

	if texturePath == nil then
		local sourceInfo = FindSkillSource( unitId, message )
		if sourceInfo ~= nil then
			message = sourceInfo.Text or message
			texturePath = sourceInfo.Image or texturePath
		end
	end

	if message == nil then
		return
	end

	skillProcInfo.Message = message
	skillProcInfo.TexturePath = texturePath
	skillProcInfo.Value = value

	if skillFeedbackDisplaying then
		if not CanQueueSkillProcFeedback( skillProcInfo ) then
			-- Don't queue non-scoring messages
			return
		end
		skillFeedbackQueue[unitId] = skillProcInfo
		return
	end

	DoSkillProcFeedback( unitId, skillProcInfo )

end

function CanQueueSkillProcFeedback( skillProcInfo )

	if skillProcInfo.AppendText == "MatchMessage_Score" then
		return true
	end
	if skillProcInfo.AppendText == "MatchMessage_ScoreLimited" then
		return true
	end
	if skillProcInfo.AppendText == "MatchMessage_ScoreDebuff" then
		return true
	end
	if skillProcInfo.Message == "MatchMessage_NoBounty" then
		return true
	end
	if skillProcInfo.Message == "MatchMessage_Bounty" then
		return true
	end
	if skillProcInfo.Message == "MatchMessage_BountyMax" then
		return true
	end

	return false
end

function GetSkillProcOffset( unitId )
	local yOffset = -200
	local playerData = GetCharacterTableByObjectId( unitId )
	local SkillProcYOffsets = {
		["PlayerSmall"] = -140,
		["PlayerMedium"] = -220,
		["PlayerLarge"] = -440,
		["PlayerMediumAlt"] = -220,
		["PlayerImp"] = -130,
		["PlayerTrail"] = -180,
		["PlayerFlying"] = -310,
		["PlayerTree"] = -380,
		["PlayerMonster"] = -285,
	}

	if playerData ~= nil then
		yOffset = SkillProcYOffsets[playerData.Archetype]
	end
	return yOffset
end

function DoSkillProcFeedback( unitId, skillProcInfo )

	skillFeedbackDisplaying = true

	local feedbackRiser = SpawnObstacle({ Name = "InvisibleTarget", Group = "Scripting", DestinationId = unitId, OffsetY = GetSkillProcOffset( unitId ) })
	PlayAnimation({ Name = "SkillProcFeedbackFx", DestinationId = feedbackRiser, OffsetY = GetSkillProcOffset( unitId ) })

	if skillProcInfo.SFX ~= nil then
		PlaySound({ Name = skillProcInfo.SFX, Id = unitId })
	end

	SkillProcValue = skillProcInfo.Value
	local skillMessage = GetDisplayName({ Text = skillProcInfo.Message })
	skillMessage = skillMessage.."!"
	if skillProcInfo.AppendText ~= nil then
		skillMessage = skillMessage..GetDisplayName({ Text = skillProcInfo.AppendText })
	end

	local justification = "CENTER"
	local offsetX = 0
	if skillProcInfo.TexturePath ~= nil then
		skillMessage = "@"..skillProcInfo.TexturePath.." "..skillMessage
		justification = "LEFT"
		offsetX = -100
	end

	DisplayWorldText({
		Id = feedbackRiser, Text = skillMessage,
		Justification = justification,
		OffsetX = offsetX,
		OutlineThickness = 3.0,
		OutlineColor = {0.20, 0.20, 0.20, 1.0},
		Color = {0.95, 0.95, 0.95, 1},
		Font = "AlegreyaSCBold",
		FontSize = 24,
		TextSymbolScale = 0.4
		})

	Move({ Id = feedbackRiser, Angle = 90, Speed = 75 })
	PlaySound({ Name = "/SFX/Menu Sounds/WaxDown", Id = feedbackRiser })

	wait(0.8)
	RemoveWorldText({ DestinationId = feedbackRiser, Duration = 0.4 })
	wait(0.41)
	Delete({ Id = feedbackRiser })

	if IsEmpty( skillFeedbackQueue ) then
		skillFeedbackDisplaying = false
		return
	end

	for queuedId, queuedSkillProcInfo in pairs( skillFeedbackQueue ) do
		skillFeedbackQueue[queuedId] = nil
		DoSkillProcFeedback( queuedId, queuedSkillProcInfo )
		return
	end

end

local StaminaBarOffsets = {
	["PlayerSmall"] = 100,
	["PlayerMedium"] = 100,
	["PlayerMediumAlt"] = 100,
	["PlayerLarge"] = 100,
	["PlayerImp"] = 100,
	["PlayerTrail"] = 100,
	["PlayerFlying"] = 100,
	["PlayerMonster"] = 100,
	["PlayerTree"] = 100,

}
function StaminaRechargeFeedback( unitId )

	if IsStaminaFull({ Id = unitId }) then
		return
	end

	PlaySound({ Name = "/SFX/Match SFX/StaminaRefilled", Id = unitId })


	--[[
	staminaString = "@".."GUI\\Icons\\Skills\\skill_icon_stamina"
	local characterInfo = GetCharacterTableByObjectId( unitId )
	local yOffset = StaminaBarOffsets[characterInfo.Archetype]

	DisplayWorldText({
		Id = unitId, Text = staminaString,
		Justification = "CENTER",
		OffsetX = -50,
		OffsetY = 32,
		OutlineThickness = 3.0,
		OutlineColor = {0.20, 0.20, 0.20, 1.0},
		Color = {0.95, 0.95, 0.95, 1},
		Font = "AlegreyaSCBold",
		FontSize = 24,
		TextSymbolScale = 0.4
		})

	wait(0.4)
	RemoveWorldText({ DestinationId = unitId, Duration = 0.4 })
	]]

end

function LeavingPracticeMatch()

	-- Needed if consecutive matches are played without leaving the wagon
	cameFromFreePlayMatch = false
	cameFromChallengeMatch = false

	if ChallengeData ~= nil then
		cameFromChallengeMatch = true
		PersistVariable({ Name = "cameFromChallengeMatch" })
	else
		cameFromFreePlayMatch = true
		PersistVariable({ Name = "cameFromFreePlayMatch" })

		-- start counting completed practice matches after intro
		if introComplete and countFreePlayMatch then
			if freePlayMatchesCompleted == nil then
				freePlayMatchesCompleted = 1
				PersistVariable({ Name = "freePlayMatchesCompleted" })
			else
				freePlayMatchesCompleted = freePlayMatchesCompleted + 1
			end
		end

	end

	SetPostMatchActiveStatus( TeamA, TeamB )

	CleanupMatchSounds()
	if BeyonderMusicId ~= nil then
		SetSoundCueValue({ Names = { "End", }, Id = BeyonderMusicId, Value = 1 })
		BeyonderMusicId = nil
	end
	wait(0.25)

	practiceMatch = false

	netMPMatch = false
	PersistVariable({ Name = "netMPMatch", Value = false })

end

OnTriggerFired{ "LeavePracticeMatch",
	function( triggerArgs )
		wait(0.05)
		if introMatch and TitanMode then
			FadeOutAllAmbience( 0.25 )
			StopSound({ Name = "/Ambience/MatchSiteAAmbience", Duration = 0.25 })
			FireTrigger({ Name = "EndCurrentMusic" })
			FadeOut({ Color = Color.Black })
			wait(0.27)
			cameFromIntroMatch = true
			PersistVariable({ Name = "cameFromIntroMatch" })
			SetPostMatchActiveStatus( TeamA, TeamB )
			SaveCheckpoint({ StartNextMap = "Campaign", DevSaveName = CreateReturnDevSaveName() })
			LoadMap({ Name = "Campaign" })
		else
			LeavingPracticeMatch()
			SaveCheckpoint({ StartNextMap = "Campaign", DevSaveName = CreateReturnDevSaveName() })
			LoadMap({ Name = "Campaign" })
		end
	end
}

function EndEncounterPresentation()
	-- used at least in ScenarioDefense for now [GK]
	PlaySound({ Name = "/Music/Stingers/MatchLossStinger" })
	FadeOut({ Color = Color.Black, Duration = 0.1 })
	AdjustFullscreenBloom({ Name = "Saturated", Duration = 0.2 })
	ShakeScreen({ Speed = 4, Distance = 100, Duration = 0.5 })
	Rumble({ Duration = 2, RightFraction = 0.17 })
	AdjustRadialBlurDistance({ Fraction = 1.5, Duration = 0.5 })
	AdjustRadialBlurStrength({ Fraction = 1.5, Duration = 0.5 })
	wait(2)
end

function MatchFoundPresentation()
	DisplayInfoPanelText({ Text = "Info_MatchFound", Duration = 1 })
	FadeMusicOut({ Duration = 2.5 })

	thread( MatchFoundAudio )

	wait(1.0)
	FadeOut({ Color = Color.Black, Duration = 1.5 })
end

function VictoryShake( postMatchColorGrade )
	wait(1.45)
	--PlaySound({ Name = "/SFX/Menu Sounds/LevelUpFlash" })
	if postMatchColorGrade == nil then
		postMatchColorGrade = "MeteorStrike"
	end
	AdjustColorGrading({ Name = postMatchColorGrade, Duration = 0.2, Delay = 0.0 })
	if ascensionMatch then
		AdjustColorGrading({ Name = "Ascension", Delay = 0.2, Duration = 1.6 })
	else
		AdjustColorGrading({ Name = "Off", Delay = 0.2, Duration = 1.6 })
	end
	--AdjustFullscreenBloom({ Name = "NewType09", Duration = 0.3, Delay = 0 })
	--AdjustFullscreenBloom({ Name = "Subtle", Duration = 2.0, Delay = 0.5 })

	ShakeScreen({ Speed = 400, Distance = 6, Duration = 0.3, FalloffSpeed = 1000, Delay = 0.1 })
	AdjustRadialBlurStrength ( { Fraction = 1.8, Duration = 0.3, Delay = 0.0 } )
	AdjustRadialBlurDistance ( { Fraction = 0.9, Duration = 0.3, Delay = 0.0 } )
	AdjustRadialBlurStrength ( { Fraction = 0.0, Duration = 1.6, Delay = 0.2 })
	AdjustRadialBlurDistance ( { Fraction = 0.0, Duration = 1.6, Delay = 0.2 })
end

function RemoveIntroAmbienceObjects()
	Delete({ Name = "AmbientSound" })
end

OnPlayerDistanceAny{ "GoalB < 850",
	function( triggerArgs )
		if not matchMusic then
			return
		end

		if not allowTensionMusic then
			return
		end

		if not nearGoal then
			nearGoal = true
			SetSoundCueValue({ Id = GetMixingId({ }), Names = {"Vocals"}, Value = 1.0, Duration = 1.0 })
		end
	end
}

OnPlayerDistanceAny{ "GoalA < 850", PlayerIndex = 2,
	function( triggerArgs )
		if not matchMusic then
			return
		end

		if not allowTensionMusic then
			return
		end

		if not nearGoal then
			nearGoal = true
			SetSoundCueValue({ Id = GetMixingId({ }), Names = {"Vocals"}, Value = 1.0, Duration = 1.0 })
		end
	end
}

OnPlayerDistanceAll{ "GoalB > 1000",
	function( triggerArgs )
		if not matchMusic then
			return
		end

		if nearGoal then
			nearGoal = false
			SetSoundCueValue({ Id = GetMixingId({ }), Names = {"Vocals"}, Value = 0.0, Duration = 2.0 })
		end
	end
}

OnPlayerDistanceAny{ "GoalA > 1000", PlayerIndex = 2,
	function( triggerArgs )
		if not matchMusic then
			return
		end

		if nearGoal then
			nearGoal = false
			SetSoundCueValue({ Id = GetMixingId({ }), Names = {"Vocals"}, Value = 0.0, Duration = 2.0 })
		end
	end
}