283 lines
9.4 KiB
Ucode
283 lines
9.4 KiB
Ucode
/*
|
|
--------------------------------------------------------------
|
|
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
|
|
}
|