/*
	--------------------------------------------------------------
	KF_PlayerDialogue
	--------------------------------------------------------------
	
	Displayes player's portrait and nickname as title to act as
	if it is a player to speaks it.
    

	Author :  			PooSH
	--------------------------------------------------------------
*/

class KF_PlayerDialogue	extends KF_DialogueSpot_SE
placeable;

// Trigers when dialogue was triggered but didn't matched player conditions, so it was skipped
// You can set SkippedEvent=Tag, to force dialogue displaying, if requirements were not matched.
// In that case Intigator will be used as speaker, or random player, if instigated by non-player.
var(Events) 			name				SkippedEvent; 


var() byte 				MinPlayerCount;		
var() byte 				MaxPlayerCount;		
var() bool 				bOnlyAlivePlayers;	


/*
SpeakerName 	Unique name which dialogue assosiates with a player matching the following criterias.
				If player is already assigned to a local name, then he will be used. Otherwice
				player lookup will be made and the result assigned to a local name.
ExcludeNames	list of local names that can not be used for the current dialogue	
bUseInstigator	Use dialogue's instigator (DialogueInstigator) for the current dialogue.
bSetInstigator	Change dialogue's instigator to the chosen speaker
bForceDisplayEvents If true, DisplayingEvent and DisplayedEvent will be triggered even if dialogue is skipped.
Filters			Array of DialoguePlayerFilter instances, each of them returns a number between 0 and defined Value
				depending of specific player stats. Player with highest total value will be chosen for speaking.
MinFilterValue	Players with filter value < MinFilterValue are skipped and can't be used for displaying the dialogue.
				MinFilterValue=0 (default) turns off min value filtering! Use MinFilterValue=0.000001 to set it next to 0.
				
If no players found with a given criteria, dialogue will not be displayed, so use it with caution!

WARNING! If both bUseInstigator=True and SpeakerName was set, then use priority depends from bSetInstigator:
	If bSetInstigator=True, then SpeakerName has higher priority, 
		i.e. speaker will be chosen by name and set as instigator
	If bSetInstigator=False, then bUseInstigator has higher priority, 
		i.e. instigator will be used as a speaker, overriding the value stored by SpeakerName
*/
struct SDialogueFilter 
{
	var() name SpeakerName;
	var() name ExcludeNames[5];
	var() bool bUseInstigator;
	var() bool bSetInstigator;
	var() bool bForceDisplayEvents; 
	var() export editinline Array<DialoguePlayerFilter> Filters;
	var() float MinFilterValue;

};
var() array<SDialogueFilter> DialogueFilters;

var protected NamedPRITable NamedPRIs;

var PlayerDialogueReplicationInfo PlayerDialogueReplicationInfo;


function PostBeginPlay()
{
	local int i;
	
	super.PostBeginPlay();
	
	NamedPRIs = NamedPRITable(class'NamedPRITable'.static.InitTable(Level));
	
	if ( Level.NetMode != NM_Standalone ) {
		PlayerDialogueReplicationInfo = spawn(class'PlayerDialogueReplicationInfo', self);
		if ( PlayerDialogueReplicationInfo != none ) {
			PlayerDialogueReplicationInfo.DialogueActorName = self.name;			
		}
	}
	
	for ( i=0; i<Dialogues.length; ++i )
		Dialogues[i].Display.Dialogue_Header = ""; // names of speaking players will be put here later
}

function GetEvents(out array<name> TriggeredEvents,  out array<name>  ReceivedEvents)
{
	super.GetEvents(TriggeredEvents, ReceivedEvents);
	
	if ( SkippedEvent != '' )
		ReceivedEvents[ReceivedEvents.length] = SkippedEvent;
}

function DialogueSkipped()
{
	if ( SkippedEvent != '' )
		TriggerEvent(SkippedEvent, self, GetInstigator());
}

function Pawn GetInstigator()
{
	if ( DialogueInstigator == none )
		return none;
		
	return DialogueInstigator.Pawn;
}

function RetrieveAllPRIs(out array<PlayerReplicationInfo> PRIs)
{
	local Controller C;
	
	PRIs.length = 0;
	for ( C = Level.ControllerList; C != None; C = C.NextController ) {
		if ( C.PlayerReplicationInfo != none && PlayerController(C) != none 
				&& !C.PlayerReplicationInfo.bOnlySpectator
				&& (!bOnlyAlivePlayers || (C.Pawn != none && !C.Pawn.bDeleteMe && C.Pawn.Health > 0) ) ) 
			PRIs[PRIs.length] = C.PlayerReplicationInfo;
	}	
}

function PlayerReplicationInfo GetSpeakingPRI(int DlgIdx)
{
	local int i, j;
	local array<PlayerReplicationInfo> PRIs;
	local PlayerReplicationInfo result, NamedPRI;
	local float TopValue, FilteredValue;
	local array<float> FilteredValues;
	
	if ( DlgIdx < DialogueFilters.length ) {
		// if PRI is assigned to SpeakerName already - use it
		if ( DialogueFilters[DlgIdx].SpeakerName != '' );
			NamedPRI = NamedPRIs.GetPRI(DialogueFilters[DlgIdx].SpeakerName);
			
		if ( DialogueFilters[DlgIdx].bUseInstigator ) {
			if ( PlayerController(DialogueInstigator) != none && DialogueInstigator.PlayerReplicationInfo != none )
				result = DialogueInstigator.PlayerReplicationInfo;
			if ( NamedPRI != none && NamedPRI != result ) {
				// if bSetInstigator=True, then SpeakerName has higher priority than instigator
				if ( result == none || DialogueFilters[DlgIdx].bSetInstigator ) {
					result = NamedPRI;
					if ( DialogueFilters[DlgIdx].bSetInstigator )
						DialogueInstigator = PlayerController(NamedPRI.Owner);
				}
			}
		}
		else {
			if ( NamedPRI != none )
				result = NamedPRI;
			else {
				RetrieveAllPRIs(PRIs);
				// removed excluded names
				for ( i=0; i<5; ++i) {
					if ( DialogueFilters[DlgIdx].ExcludeNames[i] != '' ) {
						NamedPRI = NamedPRIs.GetPRI(DialogueFilters[DlgIdx].ExcludeNames[i]);
						if ( NamedPRI != none ) {
							for ( j=0; j<PRIs.length; ++j )
								if ( PRIs[j] == NamedPRI )
									PRIs.remove(j--, 1);
						}
					}
				}
				NamedPRI = none; //reset value
				
				// now PRIs contains only valid speakers
				if ( DialogueFilters[DlgIdx].Filters.length > 0 ) {
					TopValue = DialogueFilters[DlgIdx].MinFilterValue;
					// calculate total filtered value of each PRI
					for ( j=0; j<PRIs.length; ++j ) {
						FilteredValue = 0;
						for ( i=0; i<DialogueFilters[DlgIdx].Filters.length; ++i ) 
							FilteredValue += DialogueFilters[DlgIdx].Filters[i].CalcPlayerValue(self, DlgIdx, PRIs[j]);
						FilteredValues[j] = FilteredValue;
						if ( FilteredValue > TopValue || (j == 0 && TopValue == 0.f) )
							TopValue = FilteredValue;
					}
					// leave only PRIs with TopValue
					for ( j=0; j<PRIs.length; ++j ) {
						if ( TopValue - FilteredValues[j] > 0.01 ) {
							PRIs.remove(j, 1);
							FilteredValues.remove(j, 1);
							j--;							
						}
					}
				}
				result = PRIs[rand(PRIs.length)];
			}
		}
		if ( DialogueFilters[DlgIdx].SpeakerName != '' && result != NamedPRI ) {
			NamedPRI = result;
			NamedPRIs.SetPRI(DialogueFilters[DlgIdx].SpeakerName, NamedPRI);
		}
	}
	else {
		RetrieveAllPRIs(PRIs);
		result = PRIs[rand(PRIs.length)];
	}
		
	return result;
}

function bool RequirementsMatched()
{
	local int PlayerCount;
	
	if ( bOnlyAlivePlayers )
		PlayerCount = KFStoryGameInfo(Level.Game).GetTotalActivePlayers();
	else
		PlayerCount = Level.Game.NumPlayers;	
	
	return PlayerCount >= MinPlayerCount && (PlayerCount <= MaxPlayerCount || MaxPlayerCount == 0);
}

// set protrait and title from speaking player, then call TraverseDialogue
function TraverseDialogue()
{
	local bool bOriginalRandomize;
	
	if( Dialogues.length <= CurrentMsgIdx  || bTraversing || (bFinished && !bAllowRepeatDialogue) )
		return;
	
	if ( !RequirementsMatched() ) {
		DialogueSkipped();
		CurrentMsgIdx = Dialogues.length;
		return;		
	}
	
	if ( bRandomize ) {
		// we need to know dialogue index already
		bOriginalRandomize = true;
		bRandomize = false;
		CurrentMsgIdx = rand(Dialogues.length);
	}
		
	SetPlayerDialogue(CurrentMsgIdx, GetSpeakingPRI(CurrentMsgIdx));
	
	if ( Dialogues[CurrentMsgIdx].Display.Dialogue_Header == "" ) {
		if ( CurrentMsgIdx < DialogueFilters.length && DialogueFilters[CurrentMsgIdx].bForceDisplayEvents ) {
			if ( Dialogues[CurrentMsgIdx].Events.DisplayingEvent != '' )
				TriggerEvent(Dialogues[CurrentMsgIdx].Events.DisplayingEvent,self,GetInstigator());
			if ( Dialogues[CurrentMsgIdx].Events.DisplayedEvent != '' )
				TriggerEvent(Dialogues[CurrentMsgIdx].Events.DisplayedEvent,self,GetInstigator());
		}
		if ( !bOriginalRandomize ) {
			CurrentMsgIdx++;
			TraverseDialogue();
		}
	}
	else {
		super.TraverseDialogue();
	}
	
	bRandomize = bOriginalRandomize;
}

function SetPlayerDialogue(int DlgIdx, PlayerReplicationInfo SpeakerPRI)
{
	if ( DlgIdx < 0 || DlgIdx >= Dialogues.length )
		return;
		
	if ( SpeakerPRI != none ) {
		Dialogues[DlgIdx].Display.Portrait_Material = SpeakerPRI.GetPortrait();
		Dialogues[DlgIdx].Display.Dialogue_Header = SpeakerPRI.PlayerName;
		//avoid skipping a dialogue in cases when player doesn't have a name (is it possible?)
		if ( Dialogues[DlgIdx].Display.Dialogue_Header == "" ) 
			Dialogues[DlgIdx].Display.Dialogue_Header = "Fresh Meat"; 
			
		if ( PlayerDialogueReplicationInfo != none ) {
			PlayerDialogueReplicationInfo.DlgIndex = DlgIdx;
			PlayerDialogueReplicationInfo.SpeakerPRI = SpeakerPRI;
			PlayerDialogueReplicationInfo.NetUpdateTime = Level.TimeSeconds - 1;
		}
	}
	else {
		Dialogues[DlgIdx].Display.Portrait_Material = none;
		Dialogues[DlgIdx].Display.Dialogue_Header = "";
	}
}

defaultproperties
{
     bOnlyAlivePlayers=True
}
