//=============================================================================
// DeathMatchPlus.
//=============================================================================
class DeathMatchPlus expands DeathMatchGame
	localized
	config;

var() globalconfig bool bTournament;
var  bool bRequireReady;
var() globalconfig bool bNoviceMode;
var int CountDown;
var config bool bUseTranslocator;
var config bool bJumpMatch;
var localized string[255] StartUpMessage;
var localized string[255] TourneyMessage;
var localized string[200] WaitingMessage;
var localized string[32] ReadyMessage;
var localized string[32] NotReadyMessage;
var localized string[255] CountDownMessage;
var localized string[255] MinuteMessage;
var localized string[255] StartMessage;

var int NextThreshold;
var float LastTauntTime;

// Player rating variables for single player rated games
var bool bRatedGame;
var bool bFulfilledSpecial;
var float PlayerRating, AvgOpponentRating;
var int NumOpp, WinCount, LoseCount;
var PlayerPawn RatedPlayer;
var int IDnum;
var class<LadderMenu> LadderMenuType;

function PostBeginPlay()
{
	local int RealBots;

	if ( bRatedGame )
		RealBots = RemainingBots;
	Super.PostBeginPlay();
	if ( bRatedGame )
	{
		bNoviceMode = ( class'ChallengeGameMenu'.Default.Rank[IDnum] == 0 );
		bRequireReady = true;
		FragLimit = 0;
		TimeLimit = 20;
		CountDown = 5;
		RemainingBots = RealBots;
		if ( bNoviceMode )
			bUseTranslocator = false;
	}
	else if ( bTournament )
		bRequireReady = true;
	if ( bRequireReady && (FragLimit == 0) && (TimeLimit == 0) )
		TimeLimit = 20;
}

event InitGame( string[200] Options, out string[55] Error )
{
	Super.InitGame(Options, Error);

	IDnum = -1;
	IDnum = GetIntOption( Options, "RatedGame", IDnum );
	if ( IDnum >= 0 )
	{
		bRatedGame = true;
		SetRatedBots();
	}
}

function SetRatedBots()
{
	RemainingBots = Level.RecommendedEnemies;
	if ( RemainingBots == 0 )
	{
		RemainingBots = int(Level.IdealPlayerCount);
		if ( RemainingBots == 0 )
			RemainingBots = 4;
	}
}

function EndGame()
{
	local pawn P;

	if ( bRatedGame )
	{
		for ( P=Level.PawnList; P!=None; P=P.NextPawn )
			if ( P.IsA('PlayerPawn') )
			{
				PlayerPawn(P).ShowSpecialMenu("Botpack.EndOfMatch");
				break;
			}
	}
	else
		Super.EndGame();
}

function Killed(pawn killer, pawn Other, name damageType)
{
	Super.Killed(killer, Other, damageType);
	if ( (Bot(Killer) != None) && (Killer != Other) 
		&& (Level.TimeSeconds - LastTauntTime > 3) )
	{
		LastTauntTime = Level.TimeSeconds;
		Bot(killer).SendGlobalMessage(None, 'TAUNT', 0, 5);
	}
	if ( bRatedGame )
		RateVs(Other, Killer);
}

function ForceMeshesToExist()
{
	//to avoid dynamic load pauses
	// this function never called
	spawn(class'FemaleOne');
	spawn(class'MaleThree');
	spawn(class'FemaleTwo');
	spawn(class'MaleOne');
	spawn(class'MaleTwo');
	spawn(class'SkaarjPlayer');
}

event playerpawn Login
(
	string[32] Portal,
	string[120] Options,
	out string[80] Error,
	class<playerpawn> SpawnClass
)
{
	local playerpawn NewPlayer;

	if ( SpawnClass == class'UnrealSpectator' )
		SpawnClass = class'CHSpectator';

	NewPlayer = Super.Login(Portal, Options, Error, SpawnClass);

	if ( NewPlayer != None )
	{
		if ( bRequireReady && (CountDown > 0) && !NewPlayer.IsA('Spectator') )
			NewPlayer.PlayerRestartState = 'PlayerWaiting';
		else
			NewPlayer.PlayerRestartState = 'PlayerWalking';
		PlayStartUpMessage(NewPlayer);

		if ( !NewPlayer.IsA('Spectator') )
		{
			NewPlayer.PlayerReplicationInfo.VoiceType = class<VoicePack>(DynamicLoadObject("BotPack.Voice"$GetItemName(string(NewPlayer.class)), class'Class'));
			if ( NewPlayer.PlayerReplicationInfo.VoiceType == None )
				NewPlayer.PlayerReplicationInfo.VoiceType = class'VoiceMaleOne';
		}
		if ( bRatedGame )
			RatedPlayer = NewPlayer;
	}
	return NewPlayer;
}

function int ReduceDamage(int Damage, name DamageType, pawn injured, pawn instigatedBy)
{
	if (injured.Region.Zone.bNeutralZone)
		return 0;

	if ( instigatedBy == None)
		return Damage;

	if ( bHardCoreMode )
		Damage *= 1.75;

	//skill level modification
	if ( instigatedBy.IsA('Bot') && injured.IsA('PlayerPawn') )
	{
		if ( instigatedBy.Skill < 1.5 )
			Damage = Damage * (0.7 + 0.15 * instigatedBy.skill);
		if ( bNoviceMode )
			Damage *= 0.7;
	}
	return (Damage * instigatedBy.DamageScaling);
}

function Timer()
{
	local Pawn P;
	local bool bReady;

	if ( bRequireReady && (CountDown > 0) )
	{
		SetTimer(0.2, true);
		if ( (RemainingBots > 0) && AddBot() )
			RemainingBots--;
		for (P=Level.PawnList; P!=None; P=P.NextPawn )
			if ( P.IsA('PlayerPawn') )
				PlayerPawn(P).SetProgressTime(2);
		if ( ((NumPlayers == MaxPlayers) || (Level.NetMode == NM_Standalone)) 
				&& (RemainingBots <= 0) )
		{	
			bReady = true;
			if ( !bRatedGame )
				for (P=Level.PawnList; P!=None; P=P.NextPawn )
					if ( P.IsA('PlayerPawn') && !PlayerPawn(P).bReadyToPlay )
						bReady = false;
			
			if ( bReady )
			{	
				SetTimer(1.0, true);
				CountDown--;
				if ( CountDown <= 0 )
				{	
					for ( P = Level.PawnList; P!=None; P=P.nextPawn )
						if ( P.bIsPlayer )
						{
							if ( P.IsA('PlayerPawn') )
							{
								P.PlayerRestartState = 'PlayerWalking';
								P.GotoState('PlayerWalking');
								RestartPlayer(P);
							}
							else
								P.RestartPlayer();
						}
				}
				else
				{
					for ( P = Level.PawnList; P!=None; P=P.nextPawn )
						if ( P.IsA('PlayerPawn') )
							PlayerPawn(P).SetProgressMessage(CountDown$CountDownMessage);
				}
			}
			else 
			{
				for ( P = Level.PawnList; P!=None; P=P.nextPawn )
					if ( P.IsA('PlayerPawn') )
					{
						if ( PlayerPawn(P).bReadyToPlay )
							PlayerPawn(P).SetProgressMessage(WaitingMessage$ReadyMessage);
						else
							PlayerPawn(P).SetProgressMessage(WaitingMessage$NotReadyMessage);
					}
			}
		}
	}	
	else
	{
		if ( bRequireReady && (TimeLimit > 0) && (RemainingTime < NextThreshold) )
		{
			for ( P = Level.PawnList; P!=None; P=P.nextPawn )
				if ( P.IsA('PlayerPawn') )
				{
					PlayerPawn(P).SetProgressTime(3);
					PlayerPawn(P).SetProgressMessage(((RemainingTime+1)/60)$MinuteMessage);
				}
			NextThreshold -= 60;
		}
		Super.Timer();
	}
}

function Bot SpawnBot(out NavigationPoint StartSpot)
{
	local bot NewBot;
	local ChallengeBotInfo CHBotConfig;
	local int BotN;

	CHBotConfig = ChallengeBotInfo(BotConfig);
	BotN = CHBotConfig.ChooseBotInfo();
	
	// Find a start spot.
	StartSpot = FindPlayerStart(0);
	if( StartSpot == None )
	{
		log("Could not find starting spot for Bot");
		return None;
	}

	// Try to spawn the bot.
	NewBot = Spawn(CHBotConfig.CHGetBotClass(BotN),,,StartSpot.Location,StartSpot.Rotation);

	if ( (bHumansOnly || Level.bHumansOnly) && !NewBot.bIsHuman )
	{
		log("can't add non-human bot to this game");
		NewBot.Destroy();
		NewBot = None;
	}

	if ( NewBot == None )
		NewBot = Spawn(CHBotConfig.CHGetBotClass(0),,,StartSpot.Location,StartSpot.Rotation);

	if ( NewBot != None )
	{
		NewBot.PlayerReplicationInfo.Team = BotConfig.GetBotTeam(BotN);
		CHBotConfig.CHIndividualize(NewBot, BotN, NumBots);
		NewBot.ViewRotation = StartSpot.Rotation;
		// broadcast a welcome message.
		BroadcastMessage( NewBot.PlayerReplicationInfo.PlayerName$EnteredMessage, true );

		ModifyBehaviour(NewBot);
		AddDefaultInventory( NewBot );
		NumBots++;
		if ( bRequireReady && (CountDown > 0) )
			NewBot.GotoState('Dying', 'WaitingForStart');
	}

	return NewBot;
}
		
function bool AddBot()
{
	local bot NewBot;
	local NavigationPoint StartSpot;

	NewBot = SpawnBot(StartSpot);
	if ( NewBot == None )
	{
		log("Failed to spawn bot");
		return false;
	}

	StartSpot.PlayTeleportEffect(NewBot, true);

	return true;
}

function ModifyBehaviour(Bot NewBot);

function PlayStartUpMessage(PlayerPawn NewPlayer)
{

	if ( bRequireReady )
		NewPlayer.SetProgressMessage(GameName$TourneyMessage);
	else
		NewPlayer.SetProgressMessage(StartUpMessage);
}

function float PlayerJumpZScaling()
{
	if ( bJumpMatch )
		return 3;
	else if ( bHardCoreMode )
		return 1.1;
	else
		return 1.0;
}

function AddDefaultInventory( pawn PlayerPawn )
{
	local Weapon NewWeapon;

	if ( PlayerPawn.IsA('Spectator') || (bRequireReady && (CountDown > 0)) )
		return;

	Super.AddDefaultInventory(PlayerPawn);

	if ( bUseTranslocator )
	{
		// Spawn Translocator.
		if( PlayerPawn.FindInventoryType(class'Translocator')!=None )
			return;
		newWeapon = Spawn(class'Translocator');
		if( newWeapon != None )
		{
			newWeapon.BecomeItem();
			PlayerPawn.AddInventory(newWeapon);
			newWeapon.Instigator = PlayerPawn;
			newWeapon.GiveAmmo(PlayerPawn);
			newWeapon.SetSwitchPriority(PlayerPawn);
			newWeapon.WeaponSet(PlayerPawn);
		}
	}

}	

function byte AssessBotAttitude(Bot aBot, Pawn Other)
{
	if ( Other.IsA('TeamCannon') 
		|| (aBot.RelativeStrength(Other) > aBot.Aggressiveness) )
		return 0;
	else
		return 1;
}

function float GameThreatAdd(Bot aBot, Pawn Other)
{
	return 0;
}

/* ReplaceWith()
Call this function from IsRelevant to replace an actor Other with an actor of aClass.  This is 
used to allow specific gametypes to globally replace one kind of actor in the level with another
*/
function ReplaceWith(actor Other, class<actor> aClass)
{
	local Actor A;

	A = Spawn(aClass,,Other.tag,Other.Location, Other.Rotation);
	if ( A == None )
		return;
	if ( Other.IsA('Inventory') && A.IsA('Inventory') && Inventory(Other).MyMarker != None )
	{
		Inventory(Other).MyMarker.markedItem = Inventory(A);
		Inventory(A).MyMarker = Inventory(Other).MyMarker;
		Inventory(Other).MyMarker = None;
	}
	A.event = Other.event;
	A.tag = Other.tag;
}

function bool IsRelevant(actor Other) 
{
	// replace Unreal I inventory actors by their Challenge pack equivalents
	if ( bJumpMatch && Other.IsA('JumpBoots') )
		return false;


	if ( bNoviceMode )
	{
		if ( Other.IsA('WarheadLauncher') )
			return false;
	}
	else
	{
		if ( Other.IsA('AutoMag') && !Other.IsA('DoubleAutoMag') )
		{
			ReplaceWith( Other, class'DoubleAutoMag' );
			return false;
		}
	}
	return Super.IsRelevant(Other);
}

// AllowTranslocation - return true if Other can teleport to Dest
function bool AllowTranslocation(Pawn Other, vector Dest )
{
	return true;
}

function bool CanTranslocate(Bot aBot)
{
	local Translocator T;

	if ( !bUseTranslocator )
		return false;
	T = Translocator(aBot.FindInventoryType(class'Translocator'));
	return ( (T != None) && (T.TTarget == None) );
} 

function PickAmbushSpotFor(Bot aBot)
{
	local NavigationPoint N;

	for ( N=Level.NavigationPointList; N!=None; N=N.NextNavigationPoint )
		if ( N.IsA('Ambushpoint') && !N.taken 
			&& ((aBot.AmbushSpot == None)
				|| (VSize(aBot.Location - aBot.Ambushspot.Location)
					 > VSize(aBot.Location - N.Location))) )
				aBot.Ambushspot = Ambushpoint(N);
}

function RateVs(Pawn Other, Pawn Killer)
{
	local int numopp, Win;
	local float oppRating, K, We;
	Local PlayerPawn P;
	Local Bot B;

	if ( Killer.IsA('PlayerPawn') )
	{
		P = PlayerPawn(Killer);
		B = Bot(Other);
		Win = 1;
		WinCount++;
	}
	else if ( Other.IsA('PlayerPawn') )
	{
		LoseCount++;
		P = PlayerPawn(Other);
		B = Bot(Killer);
	}
	else
		return;

	if ( B == None )
		oppRating = PlayerRating - 400;
	else
		oppRating = FMin(B.GetRating(), PlayerRating + 400);

	numopp++;
	AvgOpponentRating = (AvgOpponentRating * (numopp - 1) + oppRating)/numopp;  	
	if ( numopp < 20 )
	{
		PlayerRating = AvgOpponentRating + 400 * (WinCount - LoseCount)/numopp;			
	}
	else
	{
		if ( oppRating < PlayerRating - 400 )
			return;
		
		if ( PlayerRating < 2100 )
			K = 32;
		else if ( PlayerRating < 2400 )
			K = 24;
		else 
			K = 16; 

		We = 1/(10^((PlayerRating - opprating)/400) + 1);
		PlayerRating = PlayerRating + K * (Win - We);

		/* FOLLOWING NOT DONE - do at end of level FIXME
		Pre-tournament Rating Post-tournament Rating   
		0-2099  2100-2399 Ra = 2100 + (Rn-2100) x 0.75  
		2100-2399 0-2099  Ra = 2100 + (Rn-2100) x 1.33  
		2100-2399 2400-3000 Ra = 2400 + (Rn-2400) x 0.66  
		2400-3000  2100-2399  Ra = 2400 + (Rn-2400) x 1.50 
		*/
	}
}

function bool SuccessfulGame()
{
	local Pawn P;

	for ( P=Level.PawnList; P!=None; P=P.NextPawn )
		if ( P.bIsPlayer && (P != RatedPlayer) )
			if ( P.PlayerReplicationInfo.Score >= RatedPlayer.PlayerReplicationInfo.Score )
				return false;

	return true;
}

defaultproperties
{
     CountDown=15
     StartUpMessage="Challenge DeathMatch.  Kill anything that moves!"
     TourneyMessage=" - Tournament Mode.  Waiting for other players."
     WaitingMessage="Waiting for ready signals (Use your fire button to toggle ready). You are "
     ReadyMessage="READY!"
     NotReadyMessage="NOT READY!"
     CountDownMessage=" seconds until play starts"
     MinuteMessage=" minutes remaining!"
     NextThreshold=300
     GameMenuType=Class'BotPack.ChallengeDMOptionsMenu'
     HUDType=Class'BotPack.ChallengeHUD'
     BeaconName="CDM"
     GameName="Challenge DeathMatch"
     BotConfigType=Class'BotPack.ChallengeBotInfo'
	 LadderMenuType=Class'BotPack.DMLadder'
}
