rott/kf_sources/ScrnStoryGame/Classes/KF_PlayerDialogue.uc
2026-07-14 20:27:09 +07:00

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
}