2242 lines
72 KiB
Ucode
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"
|
|
}
|