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

2242 lines
72 KiB
Ucode

/*
--------------------------------------------------------------
KFStoryGameInfo
--------------------------------------------------------------
GameType scripts for 'Story' maps.
Author : Alex Quick
--------------------------------------------------------------
*/
class KFStoryGameInfo extends KFGameType;
/* === Objective Structs & Enums ============================================
==============================================================================*/
enum EFontScale
{
Font_Small,
Font_Medium,
Font_Large,
Font_VeryLarge,
};
/* 2D vector for storing draw coordinates on the canvas */
struct SVect2D
{
var() float Horizontal,Vertical;
};
struct SObjectiveHeaderInfo
{
var () localized string Header_Text;
var () color Header_Color;
var () KFStoryGameInfo.EFontScale Header_Scale;
};
enum EBackGroundAspectRatio
{
Aspect_Stretched,
Aspect_Scaled,
Aspect_FromTexture,
};
struct SObjectiveBackgroundInfo
{
var () Material Background_Texture;
var () Material Background_Texture_Collapsed;
var () float Background_Padding;
var () EBackgroundAspectRatio Background_AspectRatio;
var float Background_Scale;
var () SVect2D BackGround_Offset;
};
/* A struct representing an event that can be fired off at various intermediate stages of completion (or failure) */
struct SObjectiveProgressEvent
{
/* name of event to fire off */
var () name EventName;
var () float ProgressPct;
var () bool bReTriggerable;
var bool bWasTriggered;
var float LastTriggeredPct;
};
/* == Conditions ==============================================================*/
enum EConditionInitialState
{
Inactive,
Active,
};
enum EConditionActivationMethod
{
Null,
TriggerToggled,
TriggerActivates,
TriggerDeActivates,
RandomlyActivate,
};
/* The importance of a conditions' progress to the Parent Objective's completion status
i.e In a situation where there are two success conditions with equal progress importance, both
will have to be completed before the objective is considered complete.
In a situation where there are 10 failure conditions but one of them is marked 'Critical' , the
objective would be considered failed at any point where that single condition was marked complete.
*/
enum EProgressImportance
{
Normal,
Critical,
Ignore,
};
enum EDisplayStyle
{
Count_Up,
Count_Down,
Hide_Counter,
};
enum EHintDisplayStyle
{
HDS_Hide,
HDS_BarOnly,
HDS_TextOnly,
HDS_Combination,
};
struct long SConditionDifficultyScale
{
var () float Scale_Beginner;
var () float Scale_Hard;
var () float Scale_Suicidal;
var () float Scale_HellOnEarth;
};
struct long SConditionPlayerCountScale
{
var() float Scale_1P;
var() float Scale_2P;
var() float Scale_3P;
var() float Scale_4P;
var() float Scale_5P;
var() float Scale_6P;
};
struct long SDifficultyWrapper
{
var () SConditionDifficultyScale Scale_GameDifficulty;
var () SConditionPlayerCountScale Scale_PlayerCount;
};
struct SConditionHintInfoHUD
{
var () localized string Screen_Hint;
var () EDisplayStyle Screen_CountStyle;
var () EHintDisplayStyle Screen_ProgressStyle;
var () KFStoryGameInfo.EFontScale FontScale;
var () bool bShowCheckBox;
var () bool bShowStrikethrough;
var () Material Screen_ProgressBarBG;
var () Material Screen_ProgressBarFill;
var () color Screen_Clr;
};
struct SConditionHintInfoWorld
{
var () Material World_Texture;
var () float World_Texture_Scale;
var () localized string World_Hint;
var () private edfindable Actor World_Location;
var () bool bShowWhispTrail;
var () bool bHide;
var () bool bIgnoreWorldLocHidden;
var () color World_Clr;
var () color Whisp_Clr;
};
/* === Wave / Squad Designer Structs & Enums =================================
==============================================================================*/
enum EZEDSpawnPriority
{
Normal,
Low,
VeryLow,
High,
VeryHigh
};
/* ZED Archetypes */
enum EZEDType
{
ZED_Clot,
ZED_Bloat,
ZED_Crawler,
ZED_Gorefast,
ZED_Stalker,
ZED_Husk,
ZED_Siren,
ZED_Scrake,
ZED_Fleshpound,
ZED_Patriarch,
ZED_CUSTOM,
};
struct SZEDSquadSpawnType
{
var transient class<KFMonster> ZEDClass; // assigned dynamically in code, not used by the level designer.
var() int NumToSpawn; // number of zeds to spawn of the supplied type.
var() EZEDType ZEDType; // ZED archetype to spawn. gets modified if we're running an event.
var() class<KFMonster> CustomZEDClass; // if ZEDType is CUSTOM, spawn this class of ZED.
};
struct SZEDSquadType
{
var() string Squad_Name; // human-readable name for this squad.
var() array<SZedSquadSpawnType> Squad_ZEDs; // the types of enemies this squad can spawn.
var EZEDSpawnPriority Squad_Priority; // the likelihood of a wave designer choosing this squad over another.
var bool bIgnoreLevelMaxZEDs; // spawn this squad even if the number of ZEDS is capped out.
var float MinTimeBetweenSpawns; // the minimum time (in seconds) which must pass before this squad can be spawned again.
var float LastSquadSpawnTime; // Time at which this squad was last spawned.
};
var array<string> AllSquadNames;
var array<SZEDSquadType> AllSquads;
/* === Dialogue Structs & Enums ==============================================
==============================================================================*/
struct SVoiceOver
{
var() edfindable actor SourceActor;
var() sound VoiceOverSound;
};
enum EBroadcastScope
{
AllPlayers, // Dialogue shows to all human players on the server.
InstigatorOnly, // Dialogue only shows to the player who instigated it (Triggered the required event, or DlgActor )
};
enum EDialogueAlignment
{
Left,
Centered,
Right
};
enum EDialogueScaleStyle
{
Stretched,
Scaled
};
struct SDialogueDisplayInfo
{
var() EDialogueScaleStyle Screen_Scaling;
var EDialogueAlignment ScreenAlignment;
var() Material Screen_BGMaterial;
var() SVect2D Screen_Position;
var() Material Portrait_Material;
var() string Portrait_BinkMovie;
var() localized string Dialogue_Header;
var() localized string Dialogue_Text;
};
struct SDialogueEvent
{
var() name RequiredEvent; // Event required to trigger this dialogue.
var() name DisplayedEvent; // Event that triggers after this dialogue has been displayed
var() name DisplayingEvent; // Event that triggers just as this dialogue is being displayed.
};
struct SDialogueEntry
{
var() SDialogueDisplayInfo Display;
var() SDialogueEvent Events;
var() EBroadCastScope BroadcastScope;
var() SVoiceOver VoiceOver; // voice over to play alongside dialogue text.
var() bool bLooping; // if true, this message will continue to display until Required event happens
var Dialogue_EventListener EventListener; // event listener actor - Used if 'RequiredEvent' is set. Sets bWasTriggered when it receives the event associated with this Dialgoeu
var bool bWasTriggered; //
};
/* ============================================================================
==============================================================================*/
/* level rules actored tailored to the story gametype. Can be used by mappers to configure things like Player default equipment */
var KFLevelRules_Story StoryRules;
/* Style manager for our HUD */
var KF_HUDStyleManager HUDManager;
/* cached reference to the last activated CheckPoint volume - if the volume has bRespawnOnWipe, the game will not end when the last player dies.
Instead, everyone they will respawn at one of the playerstarts encompassed by this volume */
var KF_StoryCheckPointVolume CurrentCheckPoint;
var array<KF_StoryCheckPointVolume> AllCheckPoints;
var array<KF_DialogueSpot> AllDialogue;
var array<KF_StoryWaveDesigner> AllWaveDesigners;
/* reference to the current active Objective in a story mission */
var KF_StoryObjective CurrentObjective,LastObjective;
var int CurrentObjectiveIdx;
var string CurrentMusicTrack;
/* cached references to Objective Actors in the map so we don't have to use Actor iterators all the time */
var array<KF_StoryObjective> AllObjectives,SortedObjectives;
var class<LocalMessage> CheckPointMessageClass;
var bool bPendingTeamRespawn;
var bool bInitStoryMatch;
var array<KF_UseableMover> AllUseableMovers;
var array<KFTraderDoor> AllTraderDoors;
/* Matinee Vars ==============================================================================================*/
var bool bPlayingCinematic;
/* ===========================================================================================================*/
/* Debugging *=================================================================================================
==============================================================================================================*/
var bool bDebugPlayerSpawning;
var bool bSkipDialogue;
var KF_StoryObjective ForcedTargetObj;
// Force slomo for a longer period of time when the boss dies
/* Let's skip this in Story Mode
function DoBossDeath(){}*/
// stub - implementation in state MatchInProgress
function SetupPickups();
function GoToForcedObj();
function OnObjectiveCompleted(KF_StoryObjective CompletedObj);
function OnObjectiveFailed(KF_StoryObjective FailedObj);
event PostLogin( PlayerController NewPlayer )
{
Super.PostLogin(NewPlayer);
OnPlayerJoined(NewPlayer);
}
function Logout( Controller Exiting )
{
Super.Logout(Exiting);
OnPlayerLeft(Exiting);
}
function OnPlayerJoined(Controller JoiningPlayer)
{
if(CurrentObjective != none)
{
CurrentObjective.OnPlayerJoined(JoiningPlayer);
}
}
function OnPlayerLeft(Controller LeavingPlayer)
{
if(CurrentObjective != none)
{
CurrentObjective.OnPlayerLeft(LeavingPlayer);
}
}
function OnObjPopulationComplete()
{
local int i;
for(i = 0 ; i < AllObjectives.length ; i ++)
{
AllObjectives[i].OnObjPopulationComplete();
}
}
event InitGame( string Options, out string Error )
{
local KF_StoryCheckPointVolume CheckPoint;
local KF_DialogueSpot Dlg;
local KF_StoryWaveDesigner Wave;
local KFTraderDoor TraderDoor;
bUsingObjectiveMode = true;
ConsoleCommand("Suppress Story_Debug");
Super.InitGame(Options, Error);
/* Cache the stuff we'll be looking up pretty frequently */
foreach AllActors(class'KFLevelRules_Story',StoryRules)
{
break;
}
foreach AllActors(class 'KF_StoryCheckPointVolume', CheckPoint)
{
AllCheckPoints[AllCheckPoints.length] = CheckPoint ;
}
foreach AllActors(class 'KF_DialogueSpot', Dlg)
{
AllDialogue[AllDialogue.length] = Dlg;
}
foreach AllActors(class 'KFTraderDoor', TraderDoor)
{
AllTraderDoors[AllTraderDoors.length] = TraderDoor;
}
foreach AllActors(class 'KF_StoryWaveDesigner', Wave)
{
AllWaveDesigners[AllWaveDesigners.length] = Wave;
}
if(StoryRules == none)
{
StoryRules = spawn(class'KFLevelRules_Story');
}
KFGameLength = GL_Custom ;
if(StoryRules != none)
{
StartingCash = StoryRules.StartingCashSum;
bNoBots = !StoryRules.bAllowBots ;
StartingCash /= (GameDifficulty/2.f) ;
}
PopulateObjectiveList();
}
/* builds a list of all the spawnable ZED squads in the current map */
function CacheSquadsFor(KF_StorySquadDesigner Squad)
{
local int i;
for(i = 0 ; i < Squad.Squads.length ; i ++)
{
if(Squad.Squads[i].Squad_Name != "")
{
AllSquadNames[AllSquadNames.length] = Squad.Squads[i].Squad_Name ;
AllSquads[AllSquads.length] = Squad.Squads[i] ;
log("Caching Squad - "@Squad.Squads[i].Squad_Name,'Story_Debug');
}
}
}
function LoadUpMonsterList()
{
local int i,idx;
local array<IMClassList> InitMList;
local KF_StorySquadDesigner StorySquads;
local class<KFMonster> NewZEDClass;
foreach AllActors(class 'KF_StorySquadDesigner', StorySquads)
{
CacheSquadsFor(StorySquads);
}
/* No Squad Designers, or Squad designers have no squads specified */
if(AllSquads.length == 0)
{
return;
}
InitMList = LoadUpMonsterListFromCollection();
for( i = 0 ; i < AllSquads.length ; i ++)
{
for(idx = 0 ; idx < AllSquads[i].Squad_ZEDs.length ; idx ++)
{
/* Convert our enum index to the one that matches what's in the 'MonsterClasses' collection */
switch( AllSquads[i].Squad_ZEDs[idx].ZEDType)
{
case ZED_Clot : NewZEDClass = InitMList[0].MClass; break;
case ZED_Bloat : NewZEDClass = InitMList[6].MClass; break;
case ZED_Crawler : NewZEDClass = InitMList[1].MClass; break;
case ZED_Gorefast : NewZEDClass = InitMList[2].MClass; break;
case ZED_Stalker : NewZEDClass = InitMList[3].MClass; break;
case ZED_Husk : NewZEDClass = InitMList[8].MClass; break;
case ZED_Siren : NewZEDClass = InitMList[7].MClass; break;
case ZED_Scrake : NewZEDClass = InitMList[4].MClass; break;
case ZED_Fleshpound : NewZEDClass = InitMList[5].MClass; break;
case ZED_Patriarch : NewZEDClass = Class<KFMonster>(DynamicLoadObject(MonsterCollection.default.EndGameBossClass,Class'Class')); break;
case ZED_CUSTOM : NewZEDClass = AllSquads[i].Squad_ZEDs[idx].CustomZEDClass; break;
}
if(NewZEDClass != none)
{
AllSquads[i].Squad_ZEDs[idx].ZEDClass = NewZEDClass ;
NewZEDClass = none;
}
else
{
log("ERROR !! Could not find suitable ZED Class im Monster Collection for -> "@GetEnum(enum 'KFStoryGameInfo.EZEDType',AllSquads[i].Squad_ZEDs[idx].ZEDType),'Story_Debug');
}
}
}
}
/* Returns the total cash amount currently held by all living members of the player's team
Used by Story Objective actors which are configured to complete when a cash goal is reached
*/
function int GetTotalCashSum()
{
local int i;
local int Total;
for(i = 0 ; i < GameReplicationInfo.PRIArray.length ; i ++)
{
Total += GameReplicationInfo.PRIArray[i].Score ;
}
return Total;
}
function int GetTotalActivePlayers()
{
local int LivingCount;
local Controller C;
for ( C=Level.ControllerList; C!=None; C=C.NextController )
{
if ((C.PlayerReplicationInfo != None) &&
C.bIsPlayer && !C.PlayerReplicationInfo.bOutOfLives &&
!C.PlayerReplicationInfo.bOnlySpectator )
{
LivingCount ++ ;
}
}
// log("TOTAL PLAYERS : "@LivingCount);
return LivingCount ;
}
function PostBeginPlay()
{
local KF_HUDStylemanager NewManager;
local KF_StoryGRI StoryGRI;
Super.PostBeginPlay();
TriggerEvent('PostBeginPlay',self,none);
if(GameReplicationInfo != none && StoryRules != none)
{
StoryGRI = KF_StoryGRI(GameReplicationInfo);
if(StoryGRI != none)
{
StoryGRI.SetVictorymaterial(StoryRules.VictoryMaterial) ;
StoryGRI.SetDefeatMaterial(StoryRules.DefeatMaterial) ;
StoryGRI.bHUDShowCash = StoryRules.bShowCash;
foreach AllActors(class 'KF_HUDStyleManager',NewManager)
{
if(HUDManager == none)
{
if(NewManager.StylePreset.StyleName == StoryRules.HUDStyle)
{
HUDManager = NewManager;
StoryGRI.SetHUDStyleManager(HUDManager);
break;
}
}
}
}
}
}
/* StartMatch()
This would be a good place to Activate any checkpoints flagged 'StartEnabled'.
*/
function StartMatch()
{
/* Needs to happen *before* we call super, as this is where the players are spawned and we need to work out
which (if any) playerstarts are associated with starting checkpoints */
EnableStartingCheckPoints();
Super.StartMatch();
if(StoryRules.bAutoStartObjectives)
{
CurrentObjectiveIdx = 0;
SetActiveObjective(SortedObjectives[CurrentObjectiveIdx], GetAHumanPlayerController().Pawn);
}
TriggerEvent('MatchInProgress', Self, GetAHumanPlayerPawn());
}
function EnableStartingCheckPoints()
{
local int i;
for( i = 0 ; i < AllCheckPoints.length ; i ++)
{
if(AllCheckPoints[i].bStartEnabled)
{
AllCheckPoints[i].CheckPointActivated(GetAHumanPlayerPawn(),true,false);
break;
}
}
}
/* A little hack so that Checkpoints which are automatically activated when the match begins can still have a valid Instigator
If they dont, activation events this checkpoint fires off may not work correctly. ie. If were trying to trigger a mover
with the checkpoint when it activates that would fail, because those actors require a human pawn to trigger them.
*/
function Pawn GetAHumanPlayerPawn()
{
local Controller C;
for ( C=Level.ControllerList; C!=None; C=C.NextController )
{
if ( PlayerController(C) != none &&
C.Pawn != none )
{
return C.Pawn ;
}
}
return none;
}
function PlayerController GetAHumanPlayerController()
{
local Controller C;
for ( C=Level.ControllerList; C!=None; C=C.NextController )
{
if ( PlayerController(C) != none)
{
return PlayerController(C);
}
}
return none;
}
/* Story Objectives =============================================================================================
==================================================================================================================*/
/* Builds a list of objective actors in the map - Linear progression objectives go in the Sorted Objectives list
everything else gets dumped in the AllObjectives array*/
function PopulateObjectiveList()
{
local KF_StoryObjective Objective;
AllObjectives.length = 0;
foreach AllActors(class 'KF_StoryObjective', Objective)
{
AllObjectives[AllObjectives.length] = Objective;
}
if(SortMissionObjectives())
{
OnObjPopulationComplete();
}
else
{
log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",'Story_Debug');
log(" WARNING - Unable to sort Map Objectives. This map won't work correctly. ",'Story_Debug');
log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",'Story_Debug');
}
}
function bool SortMissionObjectives()
{
local int i;
local KF_StoryObjective LastSortedObj;
SortedObjectives.Length = 0 ;
for(i = 0 ; i < Level.StoryObjectives.length ; i ++)
{
if(Level.StoryObjectives[i] != '')
{
LastSortedObj = FindObjectiveNamed(Level.StoryObjectives[i],true);
if(LastSortedObj != none)
{
SortedObjectives[SortedObjectives.length] = LastSortedObj ;
}
else
{
log("Warning - Could not add : "@Level.StoryObjectives[i]@" to the Sorted Objectives list. FindObjective() returned none . ",'Story_Debug');
}
}
}
return SortedObjectives.length == Level.StoryObjectives.length ;
}
function RewindToLastObjective()
{
if(LastObjective != none && LastObjective != CurrentObjective)
{
SetActiveObjective(LastObjective,CurrentObjective.Instigator);
}
}
function RestartEveryone()
{
local Controller C;
if(!WholeTeamIsWipedOut())
{
for ( C=Level.ControllerList; C!=None; C=C.NextController )
{
if ( (C.PlayerReplicationInfo != None) &&
C.bIsPlayer && C.Pawn != none )
{
C.Pawn.Died(C,class 'Suicided', C.Pawn.Location);
}
}
}
}
/* Remove dead zombies from the ZEDList array so fresh ones can spawn */
function CleanUpZEDs()
{
local KFMonster ZED;
foreach DynamicActors(class 'KFMonster', ZED)
{
ZED.Died(ZED.Controller, class'Gibbed', ZED.Location );
}
}
function bool CheckEndGame(PlayerReplicationInfo Winner, string Reason)
{
local bool Result;
/* skip the KFGameType implementation ... */
Result = Super(TeamGame).CheckEndGame(Winner,Reason);
switch(Reason)
{
case "LoseAction" :
KFGameReplicationInfo(GameReplicationInfo).EndGameType = 1;
Result = true;
break;
case "WinAction" :
GameReplicationInfo.Winner = Teams[0];
KFGameReplicationInfo(GameReplicationInfo).EndGameType = 2;
Result = true;
break;
case "LastMan" :
KFGameReplicationInfo(GameReplicationInfo).EndGameType = 1;
break;
}
return Result;
}
/* Most of the stuff in here was moved from KFGameType.CheckEndGame()
Kinda weird that the function designed to Check whether the game should end was
instead just immediately ending the game ??? */
function EndGame(PlayerReplicationInfo Winner, string Reason)
{
local PlayerController Player;
local Controller P;
local bool bSetAchievement;
local string MapName;
Super(GameInfo).EndGame(Winner,Reason);
if(bGameEnded)
{
GotoState('MatchOver');
EndTime = Level.TimeSeconds + EndTimeDelay;
if ( Reason ~= "WinAction" )
{
if ( GameDifficulty >= 2.0 )
{
bSetAchievement = true;
// Get the MapName out of the URL
MapName = GetCurrentMapName(Level);
}
}
for ( P = Level.ControllerList; P != none; P = P.nextController )
{
Player = PlayerController(P);
if ( Player != none )
{
Player.ClientSetBehindView(true);
Player.ClientGameEnded();
if ( bSetAchievement && KFSteamStatsAndAchievements(Player.SteamStatsAndAchievements) != none )
{
KFSteamStatsAndAchievements(Player.SteamStatsAndAchievements).WonGame(MapName, GameDifficulty, KFGameLength == GL_Long);
}
}
P.GameHasEnded();
}
if ( CurrentGameProfile != none )
{
CurrentGameProfile.bWonMatch = false;
}
}
log("========== END GAME : REASON : "@Reason@"::::"@bGameEnded@"===============",'Story_Debug');
log("=============================================================================",'Story_Debug');
}
/* We don't really use this function - our 'Focus' is set in EndGame() by calling Player.ClientSetBehindView(). */
function SetEndGameFocus(PlayerReplicationInfo Winner);
state MatchOver
{
function SetActiveObjective( KF_StoryObjective NewObjective, optional pawn ObjInstigator){}
function Beginstate()
{
super.BeginState();
SetActiveObjective(none);
}
}
/* Helper function for retrieving an Objective with the supplied name
optionally returns the Index of the Objective in the supplied array */
function KF_StoryObjective FindObjectiveNamed(name ObjName, optional bool bSearchAll, optional out Int ObjIndex)
{
local int i;
local array<KF_StoryObjective> ArrayToUse;
local KF_StoryObjective UnsortedObj;
/* by default just use the sorted objectives list */
ArrayToUse = SortedObjectives ;
if(bSearchAll)
{
ArrayToUse = AllObjectives;
}
for(i = 0 ; i < ArrayToUse.length ; i ++)
{
if(ArrayToUse[i].ObjectiveName == ObjName)
{
ObjIndex = i;
return ArrayToUse[i] ;
}
}
/* Iterate unsorted objectives - Last resort time. */
foreach AllActors(class 'KF_StoryObjective', UnsortedObj)
{
if(UnsortedObj.ObjectiveName == ObjName)
{
return UnsortedObj ;
}
}
log(" Warning - Failed to find objective named -"@ObjName,'Story_Debug');
return none ;
}
/* Helper function for retrieving a CheckPoint with the supplied name */
function KF_StoryCheckPointVolume FindCheckPointNamed(string CheckPointName)
{
local int i;
for(i = 0 ; i < AllCheckPoints.length ; i ++)
{
if(AllCheckPoints[i].CheckPointName ~= CheckPointName)
{
return AllCheckPoints[i];
}
}
return none;
}
function SetActiveObjective( KF_StoryObjective NewObjective, optional pawn ObjInstigator)
{
local KF_StoryGRI SGRI;
local int NewIndex;
local KF_StoryNPC StoryPawn;
local Controller C;
local name LastObjectiveName,CurrentObjectiveName;
if(NewObjective != CurrentObjective &&
(NewObjective == none || FindObjectiveNamed(NewObjective.ObjectiveName) == NewObjective &&
NewObjective.IsValidForActivation()) )
{
SGRI = KF_StoryGRI(GameReplicationInfo);
if(NewObjective != none && NewObjective.bOptional)
{
KFPlayerController(ObjInstigator.Controller).CheckForHint(61);
}
if(NewObjective != none)
{
log("Setting active game objective to -"@NewObjective.ObjectiveName,'Story_Debug');
}
if(CurrentObjective != none)
{
LastObjective = CurrentObjective;
if(LastObjective != none)
{
log("LastObjective was ... "@LastObjective.ObjectiveName,'Story_Debug');
LastObjective.Notify_ObjectiveDeActivated();
}
}
if((NewObjective == none ||
NewObjective.ObjectiveInProgressMusic == "") &&
(CurrentObjective == none || CurrentObjective.bStopMusicOnCompletion))
{
StopGameMusic();
CurrentMusicTrack = "" ;
}
CurrentObjective = NewObjective;
if(CurrentObjective != none)
{
NewIndex = GetIndexForObj(CurrentObjective);
if(NewIndex >= 0 && NewIndex < SortedObjectives.Length )
{
CurrentObjectiveIdx = NewIndex;
}
CurrentObjective.Notify_ObjectiveActivated(ObjInstigator);
/* Objective FastFoward Debug */
if(ForcedTargetObj != none)
{
/* We made it. */
if(ForcedTargetObj == CurrentObjective)
{
log("!!!!! Forced OBJ Reached. Stopping. ",'Story_Debug');
bSkipDialogue = false;
ForcedTargetObj = none;
if(SGRI != none)
{
SGRI.SetDebugTargetObj(none);
}
}
else
{
log("CurrentObjective : "@CurrentObjective.ObjectiveName@" is Not : "@ForcedTargetObj.ObjectiveName@" .. Proceeding to next OBJ by force.",'Story_Debug');
GoToForcedObj(); // keep goin'...
}
}
}
SGRI = KF_StoryGRI(GameReplicationInfo);
if(SGRI != none)
{
SGRI.SetCurrentObjective(NewObjective);
}
/* Let NPCs know that the objective changed .. */
for (c = Level.ControllerList; c!=None; c=c.nextController)
{
if(C.Pawn != none)
{
StoryPawn = KF_StoryNPC(C.Pawn);
if(StoryPawn != none)
{
if(CurrentObjective != none)
{
CurrentObjectiveName = CurrentObjective.ObjectiveName;
}
if(LastObjective != none)
{
LastObjectiveName = LastObjective.ObjectiveName;
}
StoryPawn.OnObjectiveChanged(LastObjectiveName,CurrentObjectiveName);
}
}
}
}
}
/* Displays a whisp trail leading to an Objective in story mode */
function ShowPathToObj(PlayerController P)
{
local int i;
local Objective_Whisp NewWhisp;
local Actor DestPath,TargetActor;
local vector DestLoc;
local KFPlayerController_Story StoryPC;
// Attempt to display the route to the currrent objective
if( CurrentObjective != none )
{
for(i = 0 ; i < CurrentObjective.AllConditions.length ; i ++)
{
if(CurrentObjective.AllConditions[i].ShouldShowWhispTrailFor(P))
{
DestLoc = CurrentObjective.AllConditions[i].GetWhispLocation(TargetActor) ;
DestPath = P.FindPathTo(DestLoc) ;
/* log("=====================================S=======================");
log("SHOW WHISP PATH TO : "@CurrentObjective.AllConditions[i].name);
log("Find Path to location ? "@DestPath); */
if(DestPath != none)
{
/* All these values need to be set *before* the whisp is actually spawned as the native code
assigns the particular colour from the mColorRange array the moment it is spawned. */
/* tint the whisp trail to the colour of the condition hint */
Class'Objective_Whisp'.default.mColorRange[0]=CurrentObjective.AllConditions[i].HUD_World.Whisp_Clr ;
Class'Objective_Whisp'.default.mColorRange[1]=CurrentObjective.AllConditions[i].HUD_World.Whisp_Clr ;
Class'Objective_Whisp'.default.DestLoc = DestLoc;
StoryPC = KFPlayerController_Story(P) ;
if(StoryPC != none)
{
StoryPC.SetClientWhispClr(CurrentObjective.AllConditions[i].HUD_World.Whisp_Clr);
}
NewWhisp = Spawn(class'Objective_Whisp',P,, P.Pawn.Location) ;
}
}
}
}
}
function Int GetIndexForObj(KF_StoryObjective InObj)
{
local int i;
for(i = 0 ; i < SortedObjectives.length ; i ++)
{
if(SortedOBjectives[i] == InObj)
{
return i;
}
}
log("Objective "@InObj.ObjectiveName@"is not in the sorted Objectives array. Returning invalid index",'Story_Debug');
return -1;
}
/* ================================================================================================================
==================================================================================================================*/
/* Returns true if all players are out of lives
Extended because we don't want to end the game if we have an
active Checkpoint in the map that can respawn a team when it wipes out ...
NOTE - this function gets called CONSTANTLY via. MatchInProgress.Timer()
so long as there is more than one active player in the match. Any code added here
should be structured accordingly.
*/
function bool CheckMaxLives(PlayerReplicationInfo Scorer)
{
local KF_StoryCheckPointVolume RespawnPoint;
/* Team respawn is already taking place .. Don't bother checking anything else*/
if(bPendingTeamRespawn)
{
return false;
}
if(WholeTeamIsWipedOut())
{
if(CurrentObjective != none)
{
CurrentObjective.ObjectiveFailed(Controller(Scorer.Owner),true);
}
if( CurrentCheckPoint != none &&
CurrentCheckPoint.GrantASecondChance(RespawnPoint) )
{
bPendingTeamRespawn = true;
ResetGameState();
/* Make sure the checkpoint is active so that it can respawn everyone */
if( !RespawnPoint.bIsActive )
{
RespawnPoint.CheckPointActivated(RespawnPoint.Instigator,true,false);
}
BroadcastLocalizedMessage( CheckPointMessageClass, 1, none , None, RespawnPoint );
return false ;
}
}
return Super.CheckMaxLives(Scorer);
}
/* Called by the currently active checkpoint volume when it has just finished respawning a wiped-out team
Used to update the objective to what it was at the last checkpoint area*/
function NotifyTeamRestarted()
{
local KF_StoryObjective PreviousObjective;
local int i;
local actor A;
/* Everyone should be alive and well now. - Reset their objective to whatever was current at the time they checkpointed*/
if(CurrentCheckPoint != none)
{
if(CurrentCheckPoint.RestartFromObjective != '')
{
PreviousObjective = FindObjectiveNamed(CurrentCheckPoint.RestartFromObjective) ;
if(PreviousObjective != none)
{
log("TEAM WILL RESTART FROM PREVIOUS OBJECTIVE - "@PreviousObjective.ObjectiveName,'Story_Debug');
/* Reset the Objective we're restrarting from as well as all objectives which come after it */
for(i = GetIndexForObj(PreviousObjective) ; i < SortedObjectives.length ; i ++)
{
SortedObjectives[i].Reset();
}
SetActiveObjective(PreviousObjective,CurrentObjective.Instigator);
// Hack time.
if(CurrentCheckPoint.bStartEnabled)
{
// tell all actors the game is starting
ForEach AllActors(class'Actor', A)
{
A.MatchStarting();
}
}
}
}
else
{
log(" WARNING - The checkpoint responsible for restarting this team has no 'RestartObjective'",'Story_Debug');
}
}
}
/* Called by the CheckpointVolume responsible for restarting a team that was wiped out,
just before the players actually respawn. Resets all checkpoint-approved actors in
the map to their initial state and then fires off 'Second chance' events .
*/
function ResetGameState()
{
local int i;
log("[CHECKPOINT RESTART - Resetting Game state . ]",'Story_Debug');
CleanUpZEDs();
SetActiveObjective(none);
/* Reset all relevant actors to their initial state before doing anything else */
ResetCheckPointActors();
/* get rid of stuff the players threw / dropped on the floor */
if( CurrentCheckPoint.bRemoveDroppedWeapons)
{
RemoveDroppedWeapons();
}
// re-activate LD placed ammo & weapons
SetupPickups();
for(i = 0 ; i < CurrentCheckPoint.SecondChanceEvents.length ; i ++)
{
TriggerEvent(CurrentCheckPoint.SecondChanceEvents[i],CurrentCheckPoint, CurrentCheckPoint.ActivatingPlayer.Pawn);
}
if(bDebugPlayerSpawning)
{
log("Checkpoint : "$CurrentCheckPoint.CheckPointName@"is giving everyone a second chance!",'Story_Debug');
}
}
/* reset story-relevant actors in the map to their initial state
- Note : this needs to happen BEFORE we trigger the Second Chance event */
function ResetCheckPointActors()
{
local Actor A;
local int i,idx;
local KFDoorMover KFDoor;
if(StoryRules == none)
{
log("Warning - No StoryRules actor placed in the level. Cannot reset game state",'Story_Debug');
return;
}
foreach AllActors(class 'Actor' , A)
{
/* reset by class */
for( i = 0 ; i < StoryRules.CheckpointResetClasses.length ; i ++)
{
if(A != none && ClassIsChildOf(A.class,StoryRules.CheckpointResetClasses[i]))
{
if(AllowReset(A))
{
A.Reset();
}
}
}
/* or object reference */
for(idx = 0 ; idx < CurrentCheckPoint.CheckPointResetactors.length ; idx ++ )
{
if(A != none && A == CurrentCheckPoint.CheckPointResetActors[idx])
{
if(AllowReset(A))
{
A.Reset();
}
}
}
/* re-weld DoorMovers with the 'bStartSealed' flag
this should be done in the DoorMover's Reset() function really, but it isn't. */
KFDoor = KFDoorMover(A);
if(KFDoor != none)
{
KFDoor.RespawnDoor();
// - door could also be damaged but not "dead" need to make sure its @ full weld strength if it was pre-welded
if(KFDoor.bStartSealed && KFDoor.MyTrigger != none)
{
KFDoor.MyTrigger.WeldStrength = 0;
KFDoor.MyTrigger.AddWeld(KFDoor.MaxWeld*(KFDoor.StartSealedWeldPrc/100.f),False,None);
}
}
}
}
function bool AllowReset(Actor A)
{
local int i;
if(CurrentCheckPoint != none )
{
for(i = 0 ; i < CurrentCheckPoint.ResetExcludeActors.length ; i ++)
{
if(A == CurrentCheckPoint.ResetExcludeActors[i])
{
return false;
}
}
if(CurrentCheckPoint.ForcedRestartCheckPoint != none)
{
return A != CurrentCheckPoint.ForcedRestartCheckPoint;
}
else
{
return A != CurrentCheckPoint;
}
}
return true;
}
/* Remove dropped weapon pickups from play if we're restarting a team from a checkpoint */
function RemoveDroppedWeapons()
{
local WeaponPickup WP;
foreach DynamicActors(class 'WeaponPickup' , WP)
{
if(WP.bDropped)
{
WP.Destroy();
}
}
}
/* Returns true if there are no living players left on the
team (all spectators or OutOfLives) */
function bool WholeTeamIsWipedOut()
{
local int LivingCount;
local Controller C;
for ( C=Level.ControllerList; C!=None; C=C.NextController )
{
if ( (C.PlayerReplicationInfo != None) &&
C.bIsPlayer && !C.PlayerReplicationInfo.bOutOfLives &&
!C.PlayerReplicationInfo.bOnlySpectator )
{
LivingCount ++ ;
}
}
return LivingCount <= 0;
}
/* Returns true if there is no current objective, or the current objective does
not have a currently active 'TraderTime' success condition. Used to determine whether players
should be able to switch their perks , etc. */
function bool IsTraderTime()
{
return CurrentObjective == none || CurrentObjective.IsTraderObj() || NumMonsters <= 0;
}
/* Respawns a dead Player. Extended to give Checkpoints & LevelRules
a chance to modify the stats of the guy who's respawning */
function RestartPlayer( Controller aPlayer )
{
if ( aPlayer.PlayerReplicationInfo.bOutOfLives || aPlayer.Pawn!=None )
return;
/* temporary hack ... fixme please.*/
if(KF_StoryNPC_AI(aPlayer) != none)
{
return;
}
if(bDebugPlayerSpawning)
{
ClearStayingDebugLines();
}
/* So what we're doing here is reproducing the code in KFGameType which checks 'bWaveInProgress'
Since Objective mode doesn't use that bool, we are checking IsTraderObj() which returns true if
the game is currently in trader time, and players should be able to respawn */
if( PlayerController(aPlayer)!=None && !IsTraderTime() )
{
aPlayer.PlayerReplicationInfo.bOutOfLives = True;
aPlayer.PlayerReplicationInfo.NumLives = 1;
aPlayer.GoToState('Spectating');
Return;
}
log("**********************************************************************",'Story_Debug');
log(" GAMEINFO RESTART PLAYER - "@aPlayer.PlayerReplicationInfo.PlayerName,'Story_Debug');
aPlayer.StartSpot = none; // If StartSpothas a value all the RatePlayerStart() logic gets skipped ...
Super.RestartPlayer(aPlayer);
if( aPlayer.Pawn!=None)
{
if(StoryRules!=none)
{
StoryRules.ModifyPlayer(aPlayer.Pawn);
}
/* Some story missions might be focused around survival & scrounging for health
This gives LDs the option of adding a health penalty to checkpoint respawns
*/
if(CurrentCheckPoint != none &&
!CurrentCheckPoint.bStartEnabled &&
CurrentCheckPoint.LastRespawnedPlayer == aPlayer)
{
CurrentCheckPoint.ModifyPlayer(aPlayer.Pawn);
}
}
}
/* Overriden to add allow LDs to toggle certain features of the cash reward / penalty setup for their story maps*/
function ScoreKill(Controller Killer, Controller Other)
{
local PlayerReplicationInfo OtherPRI;
local float KillScore;
OtherPRI = Other.PlayerReplicationInfo;
if ( OtherPRI != None )
{
OtherPRI.NumLives++;
// OtherPRI.Score -= ((OtherPRI.Score * (GameDifficulty * 0.05))* StoryRules.CashPenalty_Death_Modifier) ; // you Lose 35% of your current cash on Hell on Earth, 15% on normal.
// OtherPRI.Team.Score -= (OtherPRI.Score * (GameDifficulty * 0.05 * StoryRules.CashPenalty_Death_Modifier));
OtherPRI.Team.NetUpdateTime = Level.TimeSeconds - 1;
OtherPRI.bOutOfLives = true;
if( Killer!=None && Killer.PlayerReplicationInfo!=None && Killer.bIsPlayer )
BroadcastLocalizedMessage(class'KFInvasionMessage',1,OtherPRI,Killer.PlayerReplicationInfo);
else if( Killer==None || Monster(Killer.Pawn)==None )
BroadcastLocalizedMessage(class'KFInvasionMessage',1,OtherPRI);
else BroadcastLocalizedMessage(class'KFInvasionMessage',1,OtherPRI,,Killer.Pawn.Class);
CheckScore(None);
}
if ( GameRulesModifiers != None )
GameRulesModifiers.ScoreKill(Killer, Other);
if ( MonsterController(Killer) != None )
return;
if( (killer == Other) || (killer == None) )
{
if ( Other.PlayerReplicationInfo != None )
{
// Other.PlayerReplicationInfo.Score -= 1;
Other.PlayerReplicationInfo.NetUpdateTime = Level.TimeSeconds - 1;
ScoreEvent(Other.PlayerReplicationInfo,-1,"self_frag");
}
}
if ( Killer==None || !Killer.bIsPlayer || (Killer==Other) )
return;
if ( Other.bIsPlayer )
{
Killer.PlayerReplicationInfo.Score -= 5;
Killer.PlayerReplicationInfo.Team.Score -= 2;
Killer.PlayerReplicationInfo.NetUpdateTime = Level.TimeSeconds - 1;
Killer.PlayerReplicationInfo.Team.NetUpdateTime = Level.TimeSeconds - 1;
ScoreEvent(Killer.PlayerReplicationInfo, -5, "team_frag");
return;
}
if ( LastKilledMonsterClass == None )
KillScore = 1;
else if(Killer.PlayerReplicationInfo !=none)
{
KillScore = (LastKilledMonsterClass.Default.ScoringValue * StoryRules.CashReward_ZEDKills_Modifier);
// Scale killscore by difficulty
if ( GameDifficulty >= 5.0 ) // Suicidal and Hell on Earth
{
KillScore *= 0.65;
}
else if ( GameDifficulty >= 4.0 ) // Hard
{
KillScore *= 0.85;
}
else if ( GameDifficulty >= 2.0 ) // Normal
{
KillScore *= 1.0;
}
else //if ( GameDifficulty == 1.0 ) // Beginner
{
KillScore *= 2.0;
}
// Increase score in a short game, so the player can afford to buy cool stuff by the end
if( KFGameLength == GL_Short )
{
KillScore *= 1.75;
}
// KillScore = Max(1,int(KillScore)); dont clamp it in story mode. It's likely some story maps will want to do without any kill score / rewards at all.
Killer.PlayerReplicationInfo.Kills++;
ScoreKillAssists(KillScore, Other, Killer);
Killer.PlayerReplicationInfo.Team.Score += KillScore;
Killer.PlayerReplicationInfo.NetUpdateTime = Level.TimeSeconds - 1;
Killer.PlayerReplicationInfo.Team.NetUpdateTime = Level.TimeSeconds - 1;
TeamScoreEvent(Killer.PlayerReplicationInfo.Team.TeamIndex, 1, "tdm_frag");
}
if (Killer.PlayerReplicationInfo !=none && Killer.PlayerReplicationInfo.Score < 0)
Killer.PlayerReplicationInfo.Score = 0;
}
/* Use the default player load-out specified in our LevelRules actor*/
function AddGameSpecificInventory(Pawn p)
{
if(KFHumanPawn_Story(P) != none)
{
if( StoryRules!=none )
StoryRules.AddGameInv(p);
}
}
function AddDefaultInventory( pawn PlayerPawn )
{
/* Story NPC's load inventory the usual unreal way .. */
if(PlayerPawn.IsA('KF_StoryNPC'))
{
Super.AddDefaultInventory(PlayerPawn);
return;
}
if (StoryRules != none)
{
if ( UnrealPawn(PlayerPawn) != None )
{
UnrealPawn(PlayerPawn).AddDefaultInventory();
}
}
SetPlayerDefaults(PlayerPawn);
}
/* Return the 'best' player start for this player to start from.
*/
function NavigationPoint FindPlayerStart( Controller Player, optional byte InTeam, optional string incomingName )
{
local NavigationPoint Result;
local PlayerReplicationinfo PlayerPRI;
local string PlayerName;
Result = Super.FindPlayerStart(Player,InTeam,incomingName);
if(bDebugPlayerSpawning)
{
PlayerPRI = Player.PlayerReplicationinfo;
if(PlayerPRI != none)
{
PlayerName = PlayerPRI.PlayerName ;
}
else
{
PlayerName = incomingName ;
}
log(" Attempting to spawn : "@PlayerName@" at :"@Result,'Story_Debug');
}
Return Result;
}
function float RatePlayerStart(NavigationPoint N, byte Team, Controller Player)
{
local float Score;
if(bDebugPlayerSpawning)
{
if(PlayerStart(N) != none)
{
if(PlayerStart(N).bEnabled)
{
DrawStayingDebugLine(N.Location, N.Location + (100 * vect(0,0,1)), 0,255,0);
}
else
{
DrawStayingDebugLine(N.Location, N.Location + (100 * vect(0,0,1)), 255,0,0);
}
}
}
Score = Super.RatePlayerStart(N,Team,Player);
return Score;
}
State MatchInProgress
{
function BeginState()
{
Super.beginState();
SetupPickups();
}
function SetupPickups()
{
local int i;
if(StoryRules.bRandomizeWeaponPickups)
{
Super.SetupPickups();
}
else // just activate whatever's placed.
{
for(i = 0 ; i < AmmoPickups.Length; i ++)
{
if ( AmmoPickups[i].bSleeping )
{
AmmoPickups[i].GoToState('Pickup');
}
}
}
}
/* This is probably a little aggressive but .. It looks like most, if not all of the code crunching away in
KFGameType & Invasion's Timer is only really relevant to the waves / Invasion monster spawning setup */
function Timer()
{
local Controller C;
local bool OldForceRespawn;
if(!bPlayingCinematic)
{
OldForceRespawn = bForceRespawn;
bForceRespawn = false;
Super(DeathMatch).Timer();
bForceRespawn = OldForceRespawn;
}
/* Kill off stragglers - Copied from KFGameType */
if ( StoryRules != none &&
StoryRules.bAutoKillStragglers &&
NumMonsters <= StoryRules.MaxStragglers &&
TotalMaxMonsters<=StoryRules.MaxEnemiesAtOnce )
{
for ( C = Level.ControllerList; C != None; C = C.NextController )
{
if ( KFMonsterController(C)!=None && C.Pawn.Health > 0 && C.Pawn != none &&
KFMonsterController(C).CanKillMeYet())
{
C.Pawn.KilledBy( C.Pawn );
Break;
}
}
}
// this gets called from KFGameType:MatchInProgress::Timer, but we skip over
// that function call above by calling Super(DeathMatch).Timer()
if( !UpdateMonsterCount() )
{
EndGame(None,"LoseAction");
Return;
}
}
/* Debug function - quickly jump to a specific objective */
function GoToForcedObj()
{
local Controller C;
local ScriptedController SC;
local int i,idx;
if(ForcedTargetObj == none ||
CurrentObjective == ForcedTargetObj)
{
return;
}
/* Move Dialogue forwad*/
for(i = 0 ; i < AllDialogue.length ; i ++)
{
for(idx = 0 ; idx < (AllDialogue[i].Dialogues.Length-AllDialogue[i].CurrentMsgIdx) ; idx ++)
{
AllDialogue[i].TraverseDialogue();
}
}
/* Gotta move scripted sequences forward as well, in case they're pausing for Latent Actions */
for(C = Level.ControllerList ; C!= none ; C=C.nextController)
{
SC = ScriptedController(C);
if(SC != none &&
SC.SequenceScript != none &&
SC.GetStateName() == 'Scripting' &&
SC.CurrentAction != none)
{
for(i = 0 ; i < (SC.SequenceScript.Actions.length); i ++)
{
/* IF there's a MoveToGoal action in there just Teleport the dude to his destination */
if(SC.CurrentAction.MoveToGoal() && SC.Pawn != none )
{
// log("TELEPORT -> "@SC.Pawn@" to ----> "@SC.CurrentAction.GetMoveTargetFor(SC));
SC.Pawn.SetLocation(SC.CurrentAction.GetMoveTargetFor(SC).Location);
}
/* Timed Actions complete instantly */
if(SC.CurrentAction.CompleteWhenTimer())
{
SC.SetTimer(0.f,false);
}
/* No waiting for animations */
if(SC.CurrentAction.CompleteOnAnim(0))
{
SC.AnimEnd(0);
}
if(SC.CurrentAction.CompleteOnAnim(1))
{
SC.AnimEnd(1);
}
}
}
}
if(CurrentObjective != none)
{
CurrentObjective.ForceCompleteObjective();
}
/* And Turn off any active wave designers as well */
for( i = 0 ; i < AllWaveDesigners.length ; i ++)
{
for(idx = 0 ; idx < AllWaveDesigners[i].Waves.length ; idx ++)
{
if(AllWaveDesigners[i].Waves[idx].WaveController != none &&
AllWaveDesigners[i].Waves[idx].Wavecontroller.bActive)
{
AllWaveDesigners[i].Waves[idx].WaveController.AbortWave(self);
AllWaveDesigners[i].Waves[idx].WaveController.KillStragglers();
}
}
}
}
}
/* KFGameType's implementation of ReduceDamage contains a wealth of bizarre hacks ..
for simplicity's sake let's just jump back to Invasion's implementation
function int ReduceDamage(int Damage, pawn injured, pawn instigatedBy, vector HitLocation, out vector Momentum, class<DamageType> DamageType)
{
local int Result;
Result = Super(Invasion).ReduceDamage(Damage,Injured,InstigatedBy,HitLocation,Momentum,DamageType);
return Result;
}
*/
/* Differs from KillBots() in that it is not removing the bots from play, just killing their pawns.
Using this to debug Checkpoint spawning /w AIs
*/
function int ReduceDamage(int Damage, pawn injured, pawn instigatedBy, vector HitLocation, out vector Momentum, class<DamageType> DamageType)
{
local float InstigatorSkill;
local KFPlayerController PC;
local float DamageBeforeSkillAdjust;
if ( KFPawn(Injured) != none )
{
if ( KFPlayerReplicationInfo(Injured.PlayerReplicationInfo) != none && KFPlayerReplicationInfo(Injured.PlayerReplicationInfo).ClientVeteranSkill != none )
{
Damage = KFPlayerReplicationInfo(Injured.PlayerReplicationInfo).ClientVeteranSkill.Static.ReduceDamage(KFPlayerReplicationInfo(Injured.PlayerReplicationInfo), KFPawn(Injured), instigatedBy, Damage, DamageType);
}
}
// This stuff cuts thru all the B.S
if ( DamageType == class'DamTypeVomit' || DamageType == class'DamTypeWelder' || DamageType == class'SirenScreamDamage' )
{
return damage;
}
if ( instigatedBy == None )
{
return Super(xTeamGame).ReduceDamage( Damage,injured,instigatedBy,HitLocation,Momentum,DamageType );
}
if ( Monster(Injured) != None )
{
if ( instigatedBy != None )
{
PC = KFPlayerController(instigatedBy.Controller);
if ( Class<KFWeaponDamageType>(damageType) != none && PC != none )
{
Class<KFWeaponDamageType>(damageType).Static.AwardDamage(KFSteamStatsAndAchievements(PC.SteamStatsAndAchievements), Clamp(Damage, 1, Injured.Health));
}
}
return Damage;
}
if ( MonsterController(InstigatedBy.Controller) != None )
{
InstigatorSkill = MonsterController(instigatedBy.Controller).Skill;
if ( NumPlayers > 4 )
InstigatorSkill += 1.0;
if ( (InstigatorSkill < 7) && (Monster(Injured) == None) )
{
if ( InstigatorSkill <= 3 )
Damage = Damage;
else Damage = Damage;
}
}
else if ( KFFriendlyAI(InstigatedBy.Controller) != None && KFHumanPawn(Injured) != none )
Damage *= 0.25;
else if ( injured == instigatedBy )
Damage = Damage * 0.5;
if ( InvasionBot(injured.Controller) != None )
{
if ( !InvasionBot(injured.controller).bDamagedMessage && (injured.Health - Damage < 50) )
{
InvasionBot(injured.controller).bDamagedMessage = true;
if ( FRand() < 0.5 )
injured.Controller.SendMessage(None, 'OTHER', 4, 12, 'TEAM');
else injured.Controller.SendMessage(None, 'OTHER', 13, 12, 'TEAM');
}
if ( GameDifficulty <= 3 )
{
if ( injured.IsPlayerPawn() && (injured == instigatedby) && (Level.NetMode == NM_Standalone) )
Damage *= 0.5;
//skill level modification
if ( MonsterController(InstigatedBy.Controller) != None )
Damage = Damage;
}
}
if( injured.InGodMode() )
return 0;
if( instigatedBy!=injured && MonsterController(InstigatedBy.Controller)==None && (instigatedBy.Controller==None || instigatedBy.GetTeamNum()==injured.GetTeamNum()) )
{
if ( class<WeaponDamageType>(DamageType) != None || class<VehicleDamageType>(DamageType) != None )
Momentum *= TeammateBoost;
if ( Bot(injured.Controller) != None )
Bot(Injured.Controller).YellAt(instigatedBy);
if ( FriendlyFireScale==0.0 || (Vehicle(injured) != None && Vehicle(injured).bNoFriendlyFire) )
{
if ( GameRulesModifiers != None )
return GameRulesModifiers.NetDamage( Damage, 0,injured,instigatedBy,HitLocation,Momentum,DamageType );
else return 0;
}
Damage *= FriendlyFireScale;
}
// Start code from DeathMatch.uc - Had to override this here because it was reducing
// bite damage (which is 1) down to zero when the skill settings were low
if ( (instigatedBy != None) && (InstigatedBy != Injured) && (Level.TimeSeconds - injured.SpawnTime < SpawnProtectionTime)
&& (class<WeaponDamageType>(DamageType) != None || class<VehicleDamageType>(DamageType) != None) )
return 0;
Damage = super(UnrealMPGameInfo).ReduceDamage( Damage, injured, InstigatedBy, HitLocation, Momentum, DamageType );
if ( instigatedBy == None)
return Damage;
DamageBeforeSkillAdjust = Damage;
if ( Level.Game.GameDifficulty <= 3 )
{
if ( injured.IsPlayerPawn() && (injured == instigatedby) && (Level.NetMode == NM_Standalone) )
Damage *= 0.5;
}
return (Damage * instigatedBy.DamageScaling);
// End code from DeathMatch.uc
}
/*
exec function ExplodeBots(int num)
{
local int NumKilled;
local Controller C;
For ( C= Level.ControllerList; C!=None; C=C.NextController )
{
if(NumKilled == Num)
{
break;
}
if(AIController(C) != none && C.Pawn != none)
{
C.Pawn.Died(C,class 'Gibbed',C.Pawn.Location);
NumKilled ++ ;
}
}
}
*/
/*
exec function NPCGod(bool On)
{
local Controller C;
For ( C= Level.ControllerList; C!=None; C=C.NextController )
{
if(C.IsA('KF_StoryNPC_AI') ||
C.Pawn != none &&
C.Pawn.IsA('KF_StoryNPC'))
{
C.bGodMode = On;
}
}
}
*/
/* ==== Objective Debug Functions =================================*/
/*
exec function ShowObjs()
{
local int i;
for(i = 0 ; i < AllObjectives.length ; i ++)
{
Level.GetLocalPlayerController().ClientMessage("["$i+1$"]"@AllObjectives[i].ObjectiveName);
}
}
exec function FailObj()
{
if(CurrentObjective != none)
{
if(CurrentObjective.bFailed)
{
Level.GetLocalPlayerController().ClientMessage("WARNING : ["@CurrentObjective.ObjectiveName@"] is already FAILED. ");
return ;
}
CurrentObjective.ObjectiveFailed(CurrentObjective.Instigator.Controller);
}
}
*/
/* ===== Extended Zombie Spawning Support ===========================================================================
======================================================================================================================*/
function bool SpawnZEDsInVolume(ZombieVolume V, int DesiredSquadSize, optional out int NumZEDsSpawned, optional bool TryOtherVolumesOnFail )
{
local int numspawned;
local int TotalZombiesValue;
//local int i;
if(V == none || DesiredSquadSize == 0 || NextSpawnSquad.length == 0)
{
return false;
}
if( V.StorySpawnInHere(NextSpawnSquad,,numspawned,DesiredSquadSize,DesiredSquadSize,TotalZombiesValue) )
{
NumZEDsSpawned = numSpawned;
NumMonsters += numspawned;
WaveMonsters += numspawned;
// TotalMaxMonsters = DesiredMaxZEDs - NumMonsters;
// Log what the squad is after spawning
/*for(i=0; i < NextSpawnSquad.length ; i ++)
{
log("SpawnZEDsInVolume NextSpawnSquad after spawn: zombie "$NextSpawnSquad[i]$" number "$i);
}*/
return true;
}
return false;
}
/* Difficulty Scaling Accessors =============================================
============================================================================= */
/* Returns modifiers to use with the max zombie spawn count
*/
/* We're gonna pull the difficulty / player count multipliers from the editor from now on instead of hard-coding them*/
function float GetZEDCountModifier()
{
local int UsedNumPlayers;
local float PlayerCountMultiplier,DifficultyMultiplier;
PlayerCountMultiplier = 1.f;
DifficultyMultiplier = 1.f;
UsedNumPlayers = NumPlayers + NumBots ;
if(StoryRules != none)
{
// Set difficulty based values
if ( GameDifficulty >= 7.0 ) // Hell on Earth
{
DifficultyMultiplier = StoryRules.Spawn_Difficulty_Scaling.NumberOfEnemies.Scale_GameDifficulty.Scale_HellOnEarth;
}
else if ( GameDifficulty >= 5.0 ) // Suicidal
{
DifficultyMultiplier = StoryRules.Spawn_Difficulty_Scaling.NumberOfEnemies.Scale_GameDifficulty.Scale_Suicidal;
}
else if ( GameDifficulty >= 4.0 ) // Hard
{
DifficultyMultiplier = StoryRules.Spawn_Difficulty_Scaling.NumberOfEnemies.Scale_GameDifficulty.Scale_Hard;
}
else if ( GameDifficulty >= 2.0 ) // Normal
{
}
else // Beginner
{
DifficultyMultiplier = StoryRules.Spawn_Difficulty_Scaling.NumberOfEnemies.Scale_GameDifficulty.Scale_Beginner;
}
switch(UsedNumPlayers)
{
case 1 : PlayerCountMultiplier = StoryRules.Spawn_Difficulty_Scaling.NumberOfEnemies.Scale_PlayerCount.Scale_1P; break;
case 2 : PlayerCountMultiplier = StoryRules.Spawn_Difficulty_Scaling.NumberOfEnemies.Scale_PlayerCount.Scale_2P; break;
case 3 : PlayerCountMultiplier = StoryRules.Spawn_Difficulty_Scaling.NumberOfEnemies.Scale_PlayerCount.Scale_3P; break;
case 4 : PlayerCountMultiplier = StoryRules.Spawn_Difficulty_Scaling.NumberOfEnemies.Scale_PlayerCount.Scale_4P; break;
case 5 : PlayerCountMultiplier = StoryRules.Spawn_Difficulty_Scaling.NumberOfEnemies.Scale_PlayerCount.Scale_5P; break;
case 6 : PlayerCountMultiplier = StoryRules.Spawn_Difficulty_Scaling.NumberOfEnemies.Scale_PlayerCount.Scale_6P; break;
default : PlayerCountMultiplier = 0.8 * UsedNumPlayers; break; // > 6 players.
};
}
// log("GetZEDCountModifier ::: GameDifficultyModifier = "@DifficultyMultiplier@" Player Count Modifier = "@PlayerCountMultiplier);
return DifficultyMultiplier * PlayerCountMultiplier;
}
/* Returns a modifier to apply to ZED SpawnIntervals (Wave Designer & Story Volumes) */
function float GetAdjustedSpawnInterval(float BaseInterval, float UsedWaveTimeElapsed, bool bIgnoreSineMod)
{
local float SineMod;
local float FinalInterval;
local int TotalNumPlayers;
local float PlayerCountMultiplier;
local float DifficultyMultiplier;
PlayerCountMultiplier = 1.f;
SineMod = 1.0 - Abs(sin(UsedWaveTimeElapsed * SineWaveFreq));
DifficultyMultiplier = 1.f;
/* Scale the spawn interval by the number of players */
TotalnumPlayers = NumPlayers + NumBots ;
/* We're gonna pull the difficulty / player count multipliers from the editor from now on instead of hard-coding them*/
if(StoryRules != none)
{
switch(TotalnumPlayers)
{
case 1 : PlayerCountMultiplier = StoryRules.Spawn_Difficulty_Scaling.EnemySpawnRate.Scale_PlayerCount.Scale_1P; break;
case 2 : PlayerCountMultiplier = StoryRules.Spawn_Difficulty_Scaling.EnemySpawnRate.Scale_PlayerCount.Scale_2P; break;
case 3 : PlayerCountMultiplier = StoryRules.Spawn_Difficulty_Scaling.EnemySpawnRate.Scale_PlayerCount.Scale_3P; break;
case 4 : PlayerCountMultiplier = StoryRules.Spawn_Difficulty_Scaling.EnemySpawnRate.Scale_PlayerCount.Scale_4P; break;
case 5 : PlayerCountMultiplier = StoryRules.Spawn_Difficulty_Scaling.EnemySpawnRate.Scale_PlayerCount.Scale_5P; break;
case 6 : PlayerCountMultiplier = StoryRules.Spawn_Difficulty_Scaling.EnemySpawnRate.Scale_PlayerCount.Scale_6P; break;
};
// Set difficulty based values
if ( GameDifficulty >= 7.0 ) // Hell on Earth
{
DifficultyMultiplier = StoryRules.Spawn_Difficulty_Scaling.EnemySpawnRate.Scale_GameDifficulty.Scale_HellOnEarth;
}
else if ( GameDifficulty >= 5.0 ) // Suicidal
{
DifficultyMultiplier = StoryRules.Spawn_Difficulty_Scaling.EnemySpawnRate.Scale_GameDifficulty.Scale_Suicidal;
}
else if ( GameDifficulty >= 4.0 ) // Hard
{
DifficultyMultiplier = StoryRules.Spawn_Difficulty_Scaling.EnemySpawnRate.Scale_GameDifficulty.Scale_Hard;
}
else if ( GameDifficulty >= 2.0 ) // Normal
{
}
else // Beginner
{
DifficultyMultiplier = StoryRules.Spawn_Difficulty_Scaling.EnemySpawnRate.Scale_GameDifficulty.Scale_Beginner;
}
}
DifficultyMultiplier = 1.f / DifficultyMultiplier;
PlayerCountMultiplier = 1.f / PlayerCountMultiplier;
//log("Base Spawn Interval : "@BaseInterval,'Story_Debug');
if( bIgnoreSineMod )
{
//log("Sine Multiplier : Ignored",'Story_Debug');
FinalInterval = FMax(BaseInterval * PlayerCountMultiplier * DifficultyMultiplier,0.1);
}
else
{
//log("Sine Multiplier : "@SineMod,'Story_Debug');
FinalInterval = FMax((BaseInterval + (SineMod * (BaseInterval*2))) * PlayerCountMultiplier * DifficultyMultiplier,0.1) ;
}
/*log("Player Multiplier : "@PlayerCountMultiplier,'Story_Debug');
log("Difficulty Multiplier : "@DifficultyMultiplier,'Story_Debug');
log("Final Interval : "@FinalInterval);
log("UsedWaveTimeElapsed : "@UsedWaveTimeElapsed);*/
return FinalInterval;
}
/*==================================================================================
====================================================================================*/
/* looks up a squad of the supplied name in the GameInfo's cached Squads list */
function KFStoryGameInfo.SZEDSquadType FindSquadByName(string SquadName, optional out int Index)
{
local int i;
local SZEDSquadType ExportSquad;
for(i = 0 ; i < AllSquads.length ; i ++)
{
if(AllSquads[i].Squad_Name == SquadName)
{
Index = i;
ExportSquad = AllSquads[i] ;
break;
}
}
if(ExportSquad.Squad_Name == "")
{
log("Warning - could not find a suitable struct for Squad of Name : "@SquadName);
}
return ExportSquad;
}
/* looks like this function is only implemented in State MatchInProgress .. adding a stub here so we don't get weird results / crashes */
function float CalcNextSquadSpawnTime()
{
return 1.f;
}
/* Matinee Support ==============================================================================================
===================================================================================================================*/
event SceneEnded( SceneManager SM, Actor Other )
{
bPlayingCinematic = false ;
}
/* cinematic started... */
event SceneStarted( SceneManager SM, Actor Other )
{
if ( SM != none )
{
bPlayingCinematic = true;
}
}
function bool IsPlayingMatinee()
{
return bPlayingCinematic ;
}
function DramaticEvent(float BaseZedTimePossibility, optional float DesiredZedTimeDuration)
{
/* No ZED time during matinee */
if(IsPlayingMatinee())
{
return;
}
Super.DramaticEvent(BaseZedTimePOssibility);
}
function int GetCurrentWaveNum()
{
return 0;
}
function int GetFinalWaveNum()
{
return 0;
}
static event class<GameInfo> SetGameType( string MapName )
{
if ( Left(MapName, InStr(MapName, "-")) ~= "KF")
{
return class'KFGameType';
}
return super.SetGameType( MapName );
}
defaultproperties
{
CurrentObjectiveIdx = -1
bUseZEDThreatAssessment = true
TeamAIType(0)=Class'KFTeamAI_Story'
TeamAIType(1)=Class'KFTeamAI_Story'
CheckPointMessageClass=Class'KFStoryGame.Msg_CheckPoint'
bNoBots=False
DefaultEnemyRosterClass="KFStoryGame.KFStoryRoster"
HUDType="KFStoryGame.HUD_StoryMode"
ScoreBoardType="KFStoryGame.KFScoreboard_Story"
PlayerControllerClass=Class'KFStoryGame.KFPlayerController_Story'
GameReplicationInfoClass=Class'KFStoryGame.KF_StoryGRI'
Acronym="KFO"
MapPrefix="KFO"
GameName="Objective Mode"
Description="Custom objective/story mode for Killing Floor."
MapListType="KFStoryGame.KFOMapList"
MutatorClass="Engine.Mutator"
}