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

850 lines
24 KiB
Ucode

/*
--------------------------------------------------------------
KF_ObjectiveCondition
--------------------------------------------------------------
Object used to store gameplay conditions for KF_StoryObjectives
Configured by level designers.
Author : Alex Quick
--------------------------------------------------------------
*/
#exec OBJ LOAD FILE=KFStoryGame_Tex.utx
class KF_ObjectiveCondition extends Object
abstract
dependson(KFStoryGameInfo)
hidecategories(Object);
const HintCharLimit = 35;
/* Pawn responsible for Activating and / or completing this condition. */
var const name InstigatorName;
var bool bComplete,SavedbComplete;
var float LastActivatedTime;
var float LastRepTime;
/* Minimum time between client updates of condition data */
var (Network) float ConditionRepInterval;
/* HUD properties ======================================================================
=======================================================================================*/
/* HUD Properties that pertain to the world (Wisp trail, projected icons, etc.) */
var (HUD) KFStoryGameInfo.SConditionHintInfoWorld HUD_World;
/* HUD Properties that pertain to the player's On-screen display */
var (HUD) KFStoryGameInfo.SConditionHintInfoHUD HUD_Screen;
var float OldCompletionPct,NewCompletionPct;
var bool OldComplete,NewComplete;
var string OldDataString,NewDataString;
var vector OldWorldLoc,NewLocation;
/* =====================================================================================
=======================================================================================*/
var bool bActive,SavedbActive;
var bool bWasTriggered,SavedbWasTriggered;
var byte ConditionType; // 0 = failure, 1 = success , 2 = optional
/* An array that stores Condition dependencies.
ie. We only want this condition to be considered for completion if the
conditions in this list are already completed and share the same instigator as us.
an example of this would be a Key locked door */
var() array <KF_ObjectiveCondition> DependentConditions;
/* If true this objective cannot be 'uncompleted' once it is complete, without being reset */
var() bool bCompleteOnce;
var bool bLockCompletion;
var bool bForceReliableUpdate;
/* Difficulty Modifiers ============================================================*/
enum EKFGameDifficulty
{
All,
Beginner,
Normal,
Hard,
Suicidal,
HellOnEarth,
};
/* Only use this condition above MinDifficulty and below MaxDifficulty */
var(Difficulty) EKFGameDifficulty Difficulty_Min,Difficulty_Max;
var(Difficulty) int PlayerCount_Min,PlayerCount_Max;
/* Scales the Condition's requirements based on the server's difficulty setting */
var(Difficulty) KFStoryGameInfo.SConditionDifficultyScale Scale_GameDifficulty;
/* Scales the Condition's requirements based on the number of players on the server */
var(Difficulty) float Scale_PlayerCount;
var private transient KF_StoryObjective ObjOwner;
/* =====================================================================================
=======================================================================================*/
var(Events) array<KFStoryGameInfo.SObjectiveProgressEvent> ProgressEvents;
/* =====================================================================================
=======================================================================================*/
var private KF_Objective_EventListener Eventlistener;
var(Events) name Tag;
var() KFStoryGameInfo.EConditionInitialState InitialState;
var() KFStoryGameInfo.EConditionActivationMethod ActivationMethod;
var() KFStoryGameInfo.EProgressImportance ProgressImportance;
var const name InitialWorldLocActorName, WorldLocActorName;
/*========== AUDIO =====================================================================*/
var(Audio) sound Sound_Completion;
function StoppedUsing(pawn User){}
function StartedUsing(pawn User){}
function PostBeginPlay(KF_StoryObjective MyOwner)
{
SpawnEventListener(MyOwner);
// cache and clear.
if(HUD_World.World_Location != none)
{
SetTargetActor(InitialWorldLocActorName,HUD_World.World_Location);
SetTargetActor(WorldLocActorName,HUD_World.World_Location);
HUD_World.World_Location = none;
}
}
function SaveState()
{
SavedbComplete = bComplete;
SavedbActive = bActive;
SavedbWasTriggered = bWasTriggered;
}
function Actor GetTargetActor(name TargetName)
{
local Actor TargetActor;
EventListener.FindAssociatedActor(TargetName,TargetActor);
return TargetActor;
}
function SetTargetActor(name NewTargetName, Actor NewValue)
{
Eventlistener.AddAssociatedActor(NewTargetName,NewValue);
}
function ReleaseTargetActor(name ReleaseTargetName)
{
EventListener.RemoveAssociatedActor(ReleaseTargetname);
}
function Reset()
{
local int i;
if(GetObjOwner().ActiveCheckPoint == none)
{
bWasTriggered = false;
bActive = false;
bComplete = false;
}
else
{
bWasTriggered = SavedbWasTriggered;
bActive = SavedbActive;
bComplete = SavedbComplete;
}
for(i = 0 ; i < ProgressEvents.Length ; i ++)
{
ProgressEvents[i].bWasTriggered = false;
}
OldDataString = "";
OldCompletionPct = 0.f;
OldWorldLoc = vect(0,0,0);
NewLocation = vect(0,0,0);
bLockCompletion = false;
}
function Trigger(actor Other, pawn EventInstigator)
{
if(ConditionIsValid())
{
SetTargetActor(InstigatorName,EventInstigator);
switch(ActivationMethod)
{
case TriggerToggled :
if(!bActive)
{
// log("**********************************************");
// log("Activating Condition By Trigger - :"@name);
GetObjOwner().ActivateCondition(self);
return;
}
else
{
// log("**********************************************");
// log("Deactivating Condition By Trigger - :"@name);
GetObjOwner().DeActivateCondition(self);
return;
}
break;
case TriggerActivates :
if(!bActive)
{
GetObjOwner().ActivateCondition(self);
return;
}
break;
case TriggerDeActivates :
if(bActive)
{
GetObjOwner().DeActivateCondition(self);
return;
}
break;
}
}
if(bActive)
{
bWasTriggered = true;
}
}
function bool ConditionIsRelevant()
{
return true;
}
function bool ConditionIsActive()
{
// log(self@"Owner : "@GetObjOwner().ObjectiveName@" Complete ? : "@bComplete@" Active ? : "@bActive,'Story_Debug');
return GetObjOwner() != none && bActive && (!bCompleteOnce || !bComplete);
}
/* Returns true if this condition should initialize automatically when its owning objective becomes active
In most cases this will be true ... except for complex conditions which the LD intentionally wants to activate
manually (after certain events in the mission , etc.)
*/
function bool ShouldInitOnActivation()
{
local bool Result;
if(InitialState == Active ||
(ActivationMethod == RandomlyActivate &&
FRand() > 0.5)) // condition is valid for activation
{
Result = true;
}
return Result && ConditionIsValid();
}
/* Only returns true if this condition is valid for activation - based on various factors like Game Difficulty, Player Count, etc.
*/
function bool ConditionIsValid()
{
local int NumPlayers;
local float CurrentDifficulty,MaxDifficulty,MinDifficulty;
local bool Result;
Result = true;
/* =============== Check Difficulty & Player Count validitiy ======*/
/* Make sure the values are in range first .. */
PlayerCount_Min = Min(PlayerCount_Min,PlayerCount_Max);
PlayerCount_Max = Max(PlayerCount_Max,PlayerCount_Min);
if(GetObjOwner() != none)
{
NumPlayers = KFStoryGameInfo(GetObjOwner().Level.Game).GetTotalActivePlayers();
CurrentDifficulty = GetObjOwner().Level.Game.GameDifficulty;
}
if((PlayerCount_Max > 0 && NumPlayers > PlayerCount_Max) ||
(PlayerCount_Min > 0 && NumPlayers < PlayerCount_Min))
{
return false;
}
if(Difficulty_Max > 0)
{
switch(Difficulty_Max)
{
case Beginner : MaxDifficulty = 1.f ; break;
case Normal : MaxDifficulty = 2.f ; break;
case Hard : MaxDifficulty = 4.f ; break;
case Suicidal : MaxDifficulty = 5.f ; break;
case HellOnEarth : MaxDifficulty = 7.f ; break;
}
Result = CurrentDifficulty <= MaxDifficulty ;
}
if(Difficulty_Min > 0)
{
switch(Difficulty_Min)
{
case Beginner : MinDifficulty = 1.f ; break;
case Normal : MinDifficulty = 2.f ; break;
case Hard : MinDifficulty = 4.f ; break;
case Suicidal : MinDifficulty = 5.f ; break;
case HellOnEarth : MinDifficulty = 7.f ; break;
}
return Result && CurrentDifficulty >= MinDifficulty ;
}
return Result;
}
/* Returns a value that scales this condition's requirements based on the Game Difficulty setting of the server */
function float GetGameDifficultyModifier()
{
local float CurrentDifficulty,DiffModifier;
CurrentDifficulty = GetObjOwner().Level.Game.GameDifficulty;
DiffModifier = 1.f;
switch(CurrentDifficulty)
{
case 1 : DiffModifier = Scale_GameDifficulty.Scale_Beginner; break; // Beginner.
case 3 : DiffModifier = Scale_GameDifficulty.Scale_Hard; break; // Hard.
case 5 : DiffModifier = Scale_GameDifficulty.Scale_Suicidal; break; // Suicidal.
case 7 : DiffModifier = Scale_GameDifficulty.Scale_HellOnEarth; break; // Hell On Earth.
}
return DiffModifier;
}
/* Returns a value that scales this condition's requirements based on the number of players on the server */
function float GetPlayerCountModifier()
{
local int NumPlayers;
NumPlayers = KFStoryGameInfo(GetObjOwner().Level.Game).GetTotalActivePlayers();
if(NumPlayers <= 1)
{
return 1.f;
}
return FMax(NumPlayers * Scale_PlayerCount, 1.f);
}
function float GetTotalDifficultyModifier()
{
return GetGameDifficultyModifier() * GetPlayerCountModifier();
}
function ConditionActivated(pawn ActivatingPlayer)
{
SetTargetActor(InstigatorName,ActivatingPlayer);
LastActivatedTime = GetObjOwner().Level.TimeSeconds;
bActive = true;
if(GetTargetActor(InitialWorldLocActorName) != none)
{
SetTargetActor(WorldLocActorName,GetTargetActor(InitialWorldLocActorName));
}
ReliableConditionUpdate();
}
function ConditionDeActivated()
{
Reset();
}
function bool IsOptionalCondition()
{
return ConditionType == 2;
}
function SpawnEventListener(KF_StoryObjective MyOwner)
{
if(Eventlistener == none )
{
EventListener = MyOwner.Spawn(class 'KF_Objective_EventListener');
EventListener.SetConditionOwner(self);
}
}
/* Allow Completion of this Condition
always true unless there is an outstanding dependency */
function bool AllowCompletion()
{
local int i;
local bool Result;
Result = true;
for(i = 0 ; i < DependentConditions.length ; i ++)
{
if(!DependentConditions[i].bComplete ||
!DependentConditions[i].FindInstigator(GetInstigatorList()))
{
Result = false;
break;
}
}
return Result;
}
/* Some conditions may have multiple instigators */
function bool FindInstigator(array<Pawn> TestInstigators)
{
local int i,idx;
local array<Pawn> MyInstigatorList;
MyInstigatorList = GetInstigatorList();
for(i = 0 ; i < TestInstigators.length ; i ++)
{
for(idx = 0 ; idx < MyInstigatorList.length ; idx ++)
{
if(TestInstigators[i] == MyInstigatorList[idx])
{
return true;
}
}
}
return false;
}
function array<Pawn> GetInstigatorList()
{
local array<Pawn> Instigators;
Instigators[Instigators.length] = Pawn(GetTargetActor(InstigatorName));
return Instigators;
}
function ConditionTick(float DeltaTime)
{
local float PctComplete;
PctComplete = GetCompletionPct();
TriggerProgressEvents(PctComplete);
if(!bComplete)
{
if(PctComplete >= 1.f &&
AllowCompletion())
{
ConditionCompleted();
}
}
else
{
if(!bLockCompletion &&
!bCompleteOnce &&
PctComplete < 1.f)
{
bComplete = false;
}
}
/* Timed HUD Update - replicated , so do it only when the values actually change.*/
ConditionTickHUDUpdate();
}
/* Progress Event updates - Fired off at different stages in the condition's completion */
function TriggerProgressEvents(float PctComplete)
{
local int EventIdx;
for(EventIdx = 0 ; EventIdx < ProgressEvents.length ; EventIdx ++)
{
if(PctComplete >= ProgressEvents[EventIdx].ProgressPct &&
(!ProgressEvents[EventIdx].bWasTriggered ||
(ProgressEvents[EventIdx].bReTriggerable &&
PctComplete != ProgressEvents[EventIdx].LastTriggeredPct ) ) )
{
ProgressEvents[EventIdx].bWasTriggered = true;
GetObjOwner().TriggerEvent( ProgressEvents[EventIdx].EventName,GetObjOwner(),Pawn(GetTargetActor(InstigatorName)));
}
ProgressEvents[EventIdx].LastTriggeredPct = PctComplete;
}
}
function bool AllowConditionRepUpdate()
{
if( GetObjOwner().Level.TimeSeconds - LastRepTime < ConditionRepInterval ||
(HUD_World.bHide && HUD_Screen.Screen_ProgressStyle == 0 ))
{
return false;
}
return true;
}
/* Reliable HUD Update - Sends condition information to clients. Only used when a player
absolutely MUST get accurate data from this condition.
*/
function ReliableConditionUpdate(optional KFPlayerController_Story TargetPlayer)
{
local KFPlayerController_Story StoryPC;
local Controller C;
local float CompletionPct;
local string HUDHint;
local Actor NewLocActor;
GetLocation(NewLocActor);
CompletionPct = GetCompletionPct();
if(HUD_Screen.Screen_CountStyle != Hide_Counter)
{
HUDHint = GetDataString();
}
/* We want to update a specific Client's HUD */
if(TargetPlayer != none)
{
TargetPlayer.ReliableConditionUpdate(self,
GetObjOwner(),
CompletionPct,
NewLocActor,
HUDHint,
bComplete);
}
else // update everyone at once
{
For( C=GetObjOwner().Level.ControllerList; C!=None; C=C.NextController )
{
StoryPC = KFPlayerController_Story(C);
if(StoryPC != none)
{
StoryPC.ReliableConditionUpdate(self,
GetObjOwner(),
CompletionPct,
NewLocActor,
HUDHint,
bComplete);
}
}
}
}
/* Replicates relevant info for this condition to clients
VERY expensive - for this reason it is throttled and only
set to replicate dirty values
@Todo - is there a more efficient way to do this ?
*/
function ConditionTickHUDUpdate()
{
local KFPlayerController_Story StoryPC;
local Controller C;
local bool bUpdateHUD;
local Actor NewLocActor;
if((GetObjOwner().AllowConditionRepUpdate() && AllowConditionRepUpdate()) )
{
GetLocation(NewLocActor);
OldComplete = NewComplete;
NewComplete = bComplete || !bActive;
OldDataString = NewDataString;
if(HUD_Screen.Screen_CountStyle != Hide_Counter)
{
NewDataString = GetDataString();
}
OldCompletionPct = NewCompletionPct;
NewCompletionPct = GetCompletionPct();
bUpdateHUD = NewCompletionPct != OldCompletionPct ||
NewComplete != OldComplete ||
NewDataString != OldDataString;
if(bUpdateHUD)
{
LastRepTime = GetObjOwner().Level.TimeSeconds;
GetObjOwner().CurrentRepUpdates ++ ;
For( C=GetObjOwner().Level.ControllerList; C!=None; C=C.NextController )
{
StoryPC = KFPlayerController_Story(C);
if(StoryPC != none)
{
if( bForceReliableUpdate )
{
StoryPC.ReliableConditionUpdate(self,
GetObjOwner(),
NewCompletionPct,
NewLocActor,
NewDataString,
bComplete);
}
else
{
StoryPC.UnreliableConditionUpdate(self,
GetObjOwner(),
NewCompletionPct,
NewLocActor,
NewDataString,
NewComplete);
}
}
}
}
}
}
/* Set the 'bComplete' bool to notify the parent Objective that this Condition is complete */
function ConditionCompleted()
{
local actor SourceActor;
local Controller C;
/* NOTE - any completion event stuff should be done *Before* bComplete is marked true*/
if(Sound_Completion != none)
{
GetLocation(SourceActor);
if(SourceActor != none &&
SourceActor != GetObjOwner())
{
SourceActor.PlaySound(Sound_Completion,,SourceActor.SoundVolume,,SourceActor.SoundRadius,SourceActor.SoundPitch,true);
}
else
{
for (C = GetObjOwner().Level.ControllerList; C != None; C = C.NextController)
{
if(PlayerController(C) != none)
{
PlayerController(C).ClientPlaySound(Sound_Completion,true,2.f,SLOT_Talk);
}
}
// log("Warning - Attempted to play Completion sound for"@name@" with no Source Actor!",'Story_Debug');
}
}
bComplete = true;
log("==============================================================================================", 'Story_Debug');
log("============= "@self@" a condition of "@GetObjOwner().ObjectiveName@" was just marked complete. ", 'Story_Debug');
ReliableConditionUpdate();
}
/* Accessor functions ========================== */
function SetObjOwner(KF_StoryObjective NewOwner)
{
ObjOwner = NewOwner;
}
function KF_StoryObjective GetObjOwner()
{
return ObjOwner;
}
/* Returns a reference to the pawn which instigated this condition last */
function Pawn GetInstigator()
{
return Pawn(GetTargetActor(InstigatorName));
}
function int GetOwnerArrayIndex()
{
return GetObjOwner().FindIndexForCondition(self);
}
/* returns the percentage of completion for this condition */
function float GetCompletionPct()
{
return float(bComplete);
}
function string GetDataString()
{
return "" ;
}
/* Hint to display over the world icon */
function string GetWorldHint()
{
return HUD_World.World_Hint ;
}
function string GetHUDHint()
{
return ClampHUDHint(HUD_Screen.Screen_Hint);
}
function string ClampHUDHint(string Hint)
{
if( Len(Hint) > HintCharLimit )
{
Hint = left(Hint,HintCharLimit);
Hint $= "..";
}
return Hint;
}
function vector GetLocation(optional out Actor LocActor)
{
if(ConditionIsActive())
{
if(GetWorldLocActor(LocActor))
{
return LocActor.Location ;
}
LocActor = GetObjOwner();
return GetObjOwner().Location;
}
}
function bool GetWorldLocActor(out Actor LocActor)
{
local Actor HUDWorldActor;
HUDWorldActor = GetTargetActor(WorldLocActorName);
if(ConditionIsActive() &&
HUDWorldActor != none &&
!HUDWorldActor.bPendingDelete &&
!HUDWorldActor.bDeleteMe)
{
LocActor = HUDWorldActor;
return true; ;
}
return false;
}
function vector GetWhispLocation(optional out Actor LocActor)
{
return GetNearestPathNodeTo(GetLocation(LocActor)).Location;
}
function bool ShouldShowWhispTrailFor(PlayerController C)
{
return HUD_World.bShowWhispTrail;
}
function NavigationPoint GetNearestPathNodeTo( vector DesiredLocation)
{
local navigationpoint N,Closest;
local float ClosestDistSq,DistSq;
for ( N=GetObjOwner().Level.NavigationPointList; N!=None; N=N.NextNavigationPoint )
{
DistSq = VSizeSquared(N.Location - DesiredLocation) ;
if(Closest == none || DistSq < ClosestDistSq)
{
Closest = N ;
ClosestDistSq = DistSq;
}
}
return Closest;
}
/* TimeFormatting string function Copied from Scoreboard.uc */
function String FormatTime( int Seconds )
{
local int Minutes, Hours;
local String Time;
if( Seconds > 3600 )
{
Hours = Seconds / 3600;
Seconds -= Hours * 3600;
Time = Hours$":";
}
Minutes = Seconds / 60;
Seconds -= Minutes * 60;
if( Minutes >= 10 )
Time = Time $ Minutes $ ":";
else
Time = Time $ "0" $ Minutes $ ":";
if( Seconds >= 10 )
Time = Time $ Seconds;
else
Time = Time $ "0" $ Seconds;
return Time;
}
defaultproperties
{
WorldLocActorName = "HUDWorldActor"
InitialWorldLocActorName = "InitialHUDWorldActor"
InstigatorName = "Instigator"
ConditionRepInterval = 0.5
InitialState = Active
Scale_GameDifficulty=(Scale_Beginner=1,Scale_Hard=1,Scale_Suicidal=1,Scale_HellOnEarth=1)
Scale_PlayerCount=1.f
HUD_World=(World_Clr=(R=255,G=50,B=50,A=255),Whisp_Clr=(R=255,G=50,B=50,A=255),World_Texture=none/*TexOscillator'KFStoryGame_Tex.HUD.ObjArrow_osc'*/,bHide=false,World_Texture_Scale=1.f)
HUD_Screen=(Screen_Clr=(R=255,G=50,B=50,A=255),Screen_ProgressStyle=HDS_Combination,bShowStrikethrough=true,FontScale=Font_Medium,Screen_ProgressBarBG=Texture'KFStoryGame_Tex.HUD.HUd_Rectangel_W_Stroke_Neutral',Screen_ProgressBarFill=Texture 'KFStoryGame_Tex.HUD.Hud_Rectangle_W_Stroke_Fill')
}