1274 lines
46 KiB
Ucode
1274 lines
46 KiB
Ucode
class TSCGame extends ScrnGameType
|
|
config;
|
|
|
|
var TSCGameReplicationInfo TSCGRI;
|
|
var TSCVotingOptions TSCVotingOptions;
|
|
var int OriginalFinalWave;
|
|
|
|
var localized string RedTeamHumanName, BlueTeamHumanName;
|
|
|
|
var config byte OvertimeWaves; // number of Overtime waves
|
|
var config byte SudDeathWaves; // number of Sudden Death waves
|
|
var config float OvertimeTeamMoneyPenalty; // team receives less and less money for each subsequent overtime wave
|
|
var config int LateJoinerCashBonus; // extra money for late joiners to prevent, for example, joining before wave 100 with only $100 cash
|
|
var config string SongRedWin, SongBlueWin, SongBothWin, SongBothWiped;
|
|
|
|
var float CurrentTeamMoneyPenalty;
|
|
var localized string strBudgetCut;
|
|
|
|
var TSCBaseGuardian TeamBases[2];
|
|
var float BaseRadius; // Base radius
|
|
var float MinBaseZ, MaxBaseZ; // min and max Z difference between player and base
|
|
|
|
var transient int BigTeamSize, SmallTeamSize; // number of alive players in biggest team at start of the wave
|
|
var transient bool bSingleTeam; // wave started without other team
|
|
var transient bool bTeamWiped; // one of the team was wiped out during the wave. Always true when bSingleTeam=True
|
|
var transient bool bWaveEnding; // indicates end phase of the wave. Always must be checked together with bWaveBossInProgress!
|
|
var int WaveEndingCountdown; // if bWaveEnding=True, shows how many seconds left until auto-end wave (auto-kill remaining zeds)
|
|
var byte NextSquadTeam;
|
|
var bool bCheckSquadTeam; // should NextSquadTeam be checked in FindSquadTarget()?
|
|
var array < class<KFMonster> > PendingSpecialSquad;
|
|
|
|
var TSCTeam TSCTeams[2];
|
|
var class<TSCBaseGuardian> BaseGuardianClasses[2];
|
|
var ShopVolume TeamShops[2];
|
|
var class<WillowWhisp> BaseWhisp;
|
|
|
|
var bool bPendingShuffle; // shuffle teams at the end of the wave
|
|
var protected bool bTeamChanging; // indicates that game changes team members, e.g. doing shuffle
|
|
|
|
var config bool bLockTeamsOnSuddenDeath;
|
|
|
|
struct SClanTags {
|
|
var config string Prefix, Postfix;
|
|
};
|
|
var config array<SClanTags> ClanTags;
|
|
var config bool bClanCheck;
|
|
|
|
var bool bCustomHUD, bCustomScoreboard;
|
|
|
|
var bool bPvP, bNoBases;
|
|
var bool bCtryTags; // indicates if Country Tags mutator is running
|
|
|
|
var enum EHumanDamageMode
|
|
{
|
|
HDMG_None,
|
|
HDMG_NoFF,
|
|
HDMG_NoFF_PvP,
|
|
HDMG_Normal,
|
|
HDMG_PvP,
|
|
HDMG_All,
|
|
} HumanDamageMode;
|
|
|
|
var config bool bVoteHDmg, bVoteHDmgOnlyBeforeStart;
|
|
var float HDmgScale;
|
|
|
|
|
|
|
|
// this one is called from PreBeginPlay()
|
|
function InitGameReplicationInfo()
|
|
{
|
|
Super.InitGameReplicationInfo();
|
|
|
|
TSCGRI = TSCGameReplicationInfo(GameReplicationInfo);
|
|
if ( TSCGRI == none )
|
|
Warn("Wrong GameReplicationInfo class: " $ GameReplicationInfo);
|
|
else {
|
|
TSCGRI.OvertimeWaves = OvertimeWaves;
|
|
TSCGRI.SudDeathWaves = SudDeathWaves;
|
|
TSCGRI.BaseRadiusSqr = BaseRadius * BaseRadius;
|
|
TSCGRI.MinBaseZ = MinBaseZ;
|
|
TSCGRI.MaxBaseZ = MaxBaseZ;
|
|
TSCGRI.HumanDamageMode = HumanDamageMode;
|
|
TSCGRI.bSingleTeamGame = bSingleTeamGame;
|
|
}
|
|
}
|
|
|
|
event PreBeginPlay()
|
|
{
|
|
Super.PreBeginPlay();
|
|
GameReplicationInfo.bNoTeamSkins = bSingleTeamGame;
|
|
GameReplicationInfo.bNoTeamChanges = bSingleTeamGame;
|
|
}
|
|
|
|
function PostBeginPlay()
|
|
{
|
|
super.PostBeginPlay();
|
|
|
|
TSCTeams[0] = TSCTeam(Teams[0]);
|
|
TSCTeams[1] = TSCTeam(Teams[1]);
|
|
|
|
SpawnBaseGuardian(0);
|
|
Teams[0].HomeBase = TeamBases[0];
|
|
Teams[0].ColorNames[0]= RedTeamHumanName;
|
|
Teams[0].ColorNames[1]= BlueTeamHumanName;
|
|
Teams[0].TeamColor=class'Canvas'.static.MakeColor(180, 0, 0, 255);
|
|
|
|
SpawnBaseGuardian(1);
|
|
Teams[1].HomeBase = TeamBases[1];
|
|
Teams[1].ColorNames[0]= RedTeamHumanName;
|
|
Teams[1].ColorNames[1]= BlueTeamHumanName;
|
|
Teams[1].TeamColor=class'Canvas'.static.MakeColor(32, 92, 255, 255);
|
|
}
|
|
|
|
event InitGame( string Options, out string Error )
|
|
{
|
|
local ScrnVotingHandlerMut VH;
|
|
local Mutator M;
|
|
|
|
ScrnBalanceMut.bScrnWaves = true;
|
|
super.InitGame(Options, Error);
|
|
|
|
// check loaded mutators
|
|
for (M = BaseMutator; M != None; M = M.NextMutator) {
|
|
bCtryTags = bCtryTags || M.IsA('CtryTags');
|
|
}
|
|
|
|
// hard-code some ScrN features
|
|
ScrnBalanceMut.MaxVoteKillMonsters = 0; // no vote end wave since it is auto-ended in 30 seconds
|
|
ScrnBalanceMut.bSpawn0 = true;
|
|
ScrnBalanceMut.bNoStartCashToss = true;
|
|
ScrnBalanceMut.bMedicRewardFromTeam = true;
|
|
if ( ScrnBalanceMut.ForcedMaxPlayers < 12 )
|
|
ScrnBalanceMut.ForcedMaxPlayers = 0;
|
|
|
|
VH = class'ScrnVotingHandlerMut'.static.GetVotingHandler(self);
|
|
if ( VH != none ) {
|
|
TSCVotingOptions = TSCVotingOptions(VH.AddVotingOptions(class'TSCVotingOptions'));
|
|
if ( TSCVotingOptions != none ) {
|
|
TSCVotingOptions.TSC = self;
|
|
}
|
|
else
|
|
log("!!! Unable to spawn TSCVotingOptions!", 'TSC');
|
|
}
|
|
else
|
|
log("Voting (mvote) disabled.", 'TSC');
|
|
|
|
bUseEndGameBoss = false;
|
|
FinalWave = max(GetIntOption(Options, "NWaves", FinalWave), 1);
|
|
OriginalFinalWave = FinalWave;
|
|
OvertimeWaves = max(GetIntOption(Options, "OTWaves", OvertimeWaves), 0);
|
|
SudDeathWaves = max(GetIntOption(Options, "SDWaves", SudDeathWaves), 0);
|
|
|
|
// force FriendlyFireScale to 10%
|
|
FriendlyFireScale = HDmgScale;
|
|
default.FriendlyFireScale = HDmgScale;
|
|
|
|
// set MaxZombiesOnce to at least 48, unless it is a small map with only 1 trader
|
|
if ( MaxZombiesOnce < 48 && !bSingleTeamGame && ShopList.Length > 1 ) {
|
|
MaxZombiesOnce = 48;
|
|
}
|
|
|
|
// todo - allow mutators to alter those settings
|
|
if ( !bCustomHUD )
|
|
HUDType = string(Class'ScrnBalanceSrv.TSCHUD');
|
|
if ( !bCustomScoreboard )
|
|
ScoreBoardType = string(Class'ScrnBalanceSrv.TSCScoreBoard');
|
|
}
|
|
|
|
|
|
//force using DefaultEnemyRosterClass
|
|
function UnrealTeamInfo GetBlueTeam(int TeamBots)
|
|
{
|
|
local class<UnrealTeamInfo> RosterClass;
|
|
local UnrealTeamInfo Roster;
|
|
|
|
if ( DefaultEnemyRosterClass != "" ) {
|
|
RosterClass = class<UnrealTeamInfo>(DynamicLoadObject(DefaultEnemyRosterClass,class'Class'));
|
|
if ( RosterClass != none )
|
|
Roster = spawn(RosterClass);
|
|
}
|
|
if ( Roster == none )
|
|
Roster = super.GetBlueTeam(0);
|
|
|
|
return Roster;
|
|
}
|
|
|
|
function UnrealTeamInfo GetRedTeam(int TeamBots)
|
|
{
|
|
local class<UnrealTeamInfo> RosterClass;
|
|
local UnrealTeamInfo Roster;
|
|
|
|
if ( DefaultEnemyRosterClass != "" ) {
|
|
RosterClass = class<UnrealTeamInfo>(DynamicLoadObject(DefaultEnemyRosterClass,class'Class'));
|
|
if ( RosterClass != none )
|
|
Roster = spawn(RosterClass);
|
|
}
|
|
if ( Roster == none )
|
|
Roster = super.GetRedTeam(0);
|
|
|
|
return Roster;
|
|
}
|
|
|
|
// extracts ClanName from PlayerName or returns empty string, if player name
|
|
// doesn't contain clan.
|
|
function string ClanName(string PlayerName)
|
|
{
|
|
local int i, namelen, pos;
|
|
local string S;
|
|
|
|
if ( PlayerName == "" )
|
|
return "";
|
|
|
|
PlayerName = ScrnBalanceMut.StripColorTags(PlayerName);
|
|
if ( bCtryTags && Mid(PlayerName,0,1)=="[" ) {
|
|
// do not use country tags as clan names
|
|
if( Mid(PlayerName,4,1)=="]" )
|
|
PlayerName = Mid(PlayerName,5); // 3-char country code
|
|
else if( Mid(PlayerName,3,1)=="]" )
|
|
PlayerName = Mid(PlayerName,4); // 2-char country code
|
|
}
|
|
namelen = len(PlayerName);
|
|
|
|
for ( i=0; i<ClanTags.length; ++i ) {
|
|
S = PlayerName;
|
|
if ( ClanTags[i].Prefix != "" ) {
|
|
pos = InStr(PlayerName, ClanTags[i].Prefix);
|
|
if ( pos == -1 )
|
|
continue;
|
|
S = Right(S, namelen - pos - len(ClanTags[i].Prefix));
|
|
}
|
|
if ( S != "" ) {
|
|
if ( ClanTags[i].Postfix == "" )
|
|
return S;
|
|
|
|
pos = InStr(S, ClanTags[i].Postfix);
|
|
if ( pos > 0 )
|
|
return Left(S, pos);
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
// returns team, were same clan members as MyPRI are playing
|
|
// returns none if no same clan memebers are on the server
|
|
function UnrealTeamInfo MyClanTeam(PlayerReplicationInfo myPRI)
|
|
{
|
|
local int i;
|
|
local PlayerReplicationInfo PRI;
|
|
local string MyClan;
|
|
|
|
MyClan = ClanName(myPRI.PlayerName);
|
|
if ( MyClan == "" )
|
|
return none;
|
|
// debug
|
|
Broadcast(Self, ScrnBalanceMut.ColoredPlayerName(myPRI)$" associated with '"$MyClan$"' clan");
|
|
|
|
for ( i=0; i<GameReplicationInfo.PRIArray.Length; i++ ) {
|
|
PRI = GameReplicationInfo.PRIArray[i];
|
|
if ( PRI != none && PRI != myPRI && PRI.Team != none && PRI.Team.TeamIndex < 2
|
|
&& ClanName(PRI.PlayerName) == MyClan )
|
|
{
|
|
return UnrealTeamInfo(PRI.Team);
|
|
}
|
|
}
|
|
|
|
return none;
|
|
}
|
|
|
|
function bool ChangeTeam(Controller Other, int num, bool bNewTeam)
|
|
{
|
|
local UnrealTeamInfo NewTeam;
|
|
local TSCBaseGuardian gnome;
|
|
|
|
// if (CurrentGameProfile != none)
|
|
// {
|
|
// if (!CurrentGameProfile.CanChangeTeam(Other, num)) return false;
|
|
// }
|
|
|
|
if ( Other.PlayerReplicationInfo == none )
|
|
return false; // no PlayerReplicationInfo = no team -- PooSH
|
|
|
|
if ( Other.IsA('PlayerController') && Other.PlayerReplicationInfo.bOnlySpectator )
|
|
{
|
|
Other.PlayerReplicationInfo.Team = None;
|
|
return true;
|
|
}
|
|
|
|
if ( bTeamChanging && num < 2 )
|
|
NewTeam = Teams[num];
|
|
else if ( bClanCheck && !bNewTeam
|
|
&& !GameReplicationInfo.bMatchHasBegun && Other.PlayerReplicationInfo != none )
|
|
NewTeam = MyClanTeam(Other.PlayerReplicationInfo);
|
|
|
|
if ( NewTeam == none )
|
|
NewTeam = Teams[PickTeam(num,Other)];
|
|
|
|
// check if already on this team
|
|
if ( Other.PlayerReplicationInfo.Team == NewTeam )
|
|
return false;
|
|
|
|
// if player is carrying a Gnome - drop it
|
|
gnome = TSCBaseGuardian(Other.PlayerReplicationInfo.HasFlag);
|
|
if ( gnome != none ) {
|
|
gnome.Drop(vect(0,0,0));
|
|
gnome.GiveToClosestPlayer(Other.Pawn.Location);
|
|
}
|
|
// make sure player will not remain captain for the old team after the switching
|
|
if ( Other.PlayerReplicationInfo.Team != none && Other.PlayerReplicationInfo.Team.TeamIndex < 2 ) {
|
|
if ( TSCGRI.TeamCaptain[Other.PlayerReplicationInfo.Team.TeamIndex] == Other.PlayerReplicationInfo ) {
|
|
SetTeamCaptain(Other.PlayerReplicationInfo.Team.TeamIndex, none);
|
|
}
|
|
}
|
|
|
|
Other.StartSpot = None;
|
|
|
|
if ( Other.PlayerReplicationInfo.Team != None )
|
|
Other.PlayerReplicationInfo.Team.RemoveFromTeam(Other);
|
|
|
|
if ( NewTeam.AddToTeam(Other) )
|
|
{
|
|
BroadcastLocalizedMessage( GameMessageClass, 3, Other.PlayerReplicationInfo, None, NewTeam );
|
|
|
|
if ( bNewTeam && PlayerController(Other)!=None ) {
|
|
GameEvent("TeamChange",string(NewTeam.TeamIndex),Other.PlayerReplicationInfo);
|
|
// give starting cash fot team changers
|
|
GiveStartingCash(PlayerController(Other));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function byte PickTeam(byte num, Controller C)
|
|
{
|
|
local UnrealTeamInfo SmallTeam, BigTeam, NewTeam;
|
|
//local Controller B;
|
|
//local int BigTeamBots, SmallTeamBots;
|
|
|
|
if ( bSingleTeamGame && PlayerController(C) != None )
|
|
return 1; // all players go to Steampunk Team
|
|
|
|
if ( bPlayersVsBots && (Level.NetMode != NM_Standalone) )
|
|
{
|
|
if ( PlayerController(C) != None )
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
SmallTeam = Teams[0];
|
|
BigTeam = Teams[1];
|
|
|
|
if ( Teams[0].Size < Teams[1].Size ) {
|
|
SmallTeam = Teams[0];
|
|
BigTeam = Teams[1];
|
|
}
|
|
else {
|
|
SmallTeam = Teams[1];
|
|
BigTeam = Teams[0];
|
|
}
|
|
|
|
if ( SmallTeam.Size == BigTeam.Size && SmallTeam.Score > BigTeam.Score ) {
|
|
SmallTeam = BigTeam;
|
|
BigTeam = Teams[1-SmallTeam.TeamIndex];
|
|
}
|
|
|
|
if ( num < 2 )
|
|
NewTeam = Teams[num];
|
|
else
|
|
NewTeam = SmallTeam;
|
|
|
|
if ( bPlayersBalanceTeams && (Level.NetMode != NM_Standalone) && (PlayerController(C) != None) )
|
|
{
|
|
if ( SmallTeam.Size < BigTeam.Size )
|
|
NewTeam = SmallTeam;
|
|
}
|
|
|
|
return NewTeam.TeamIndex;
|
|
}
|
|
|
|
// shuffling only human players, without touching bots
|
|
// todo: or not to do? Bot support.
|
|
function ShuffleTeams()
|
|
{
|
|
local PlayerReplicationInfo PRI;
|
|
local array<PlayerReplicationInfo> RedPlayers, BluePlayers;
|
|
local int ConstantReds, ConstantBlues; // amount of player which can't change the team, e.g. because of carrying the gnome
|
|
local int i, RedTeamSize;
|
|
|
|
if ( bSingleTeamGame )
|
|
return; // no team game = no shuffle
|
|
|
|
if ( GameReplicationInfo.bMatchHasBegun ) {
|
|
if ( bWaveInProgress ) {
|
|
bPendingShuffle = true;
|
|
BroadcastLocalizedMessage(class'TSCMessages', 240);
|
|
return;
|
|
}
|
|
else if ( WaveCountDown < 10 ) {
|
|
return;
|
|
}
|
|
else if ( WaveCountDown < 30 ) {
|
|
WaveCountDown += 30;
|
|
TSCGRI.TimeToNextWave = WaveCountDown;
|
|
}
|
|
}
|
|
|
|
bPendingShuffle = false;
|
|
|
|
for ( i = 0; i < GameReplicationInfo.PRIArray.Length; ++i ) {
|
|
PRI = GameReplicationInfo.PRIArray[i];
|
|
if ( PRI != none && !PRI.bOnlySpectator && PRI.Team != none && PRI.Team.TeamIndex < 2
|
|
&& PlayerController(PRI.Owner) != none )
|
|
{
|
|
if ( PRI.HasFlag == none )
|
|
RedPlayers[RedPlayers.Length] = PRI;
|
|
else if ( PRI.Team.TeamIndex == 0 )
|
|
++ConstantReds; // can't move red gnome carrier
|
|
else
|
|
++ConstantBlues; // can't move blue gnome carrier
|
|
}
|
|
}
|
|
|
|
// now all players are in RedPlayers array. Move half to blue.
|
|
RedTeamSize = RedPlayers.length - ConstantReds + ConstantBlues;
|
|
RedTeamSize = max(0, RedTeamSize/2 + (RedTeamSize%2) * rand(2));
|
|
|
|
//Broadcast(Self, "TotalPlayers="$RedPlayers.length @ "RedTeamSize="$RedTeamSize);
|
|
//Broadcast(Self, "ConstantReds="$ConstantReds @ "ConstantBlues="$ConstantBlues);
|
|
|
|
while ( RedPlayers.length > RedTeamSize ) {
|
|
i = rand(RedPlayers.length);
|
|
BluePlayers[BluePlayers.length] = RedPlayers[i];
|
|
RedPlayers.remove(i, 1);
|
|
}
|
|
|
|
//Broadcast(Self, "RedPlayers="$RedPlayers.length @ "BluePlayers="$BluePlayers.length);
|
|
|
|
bTeamChanging = true;
|
|
for ( i=0; i < RedPlayers.length; ++i ) {
|
|
if ( RedPlayers[i].Team.TeamIndex != 0 )
|
|
PlayerController(RedPlayers[i].Owner).ServerChangeTeam(0);
|
|
}
|
|
for ( i=0; i < BluePlayers.length; ++i ) {
|
|
if ( BluePlayers[i].Team.TeamIndex != 1 )
|
|
PlayerController(BluePlayers[i].Owner).ServerChangeTeam(1);
|
|
}
|
|
bTeamChanging = false;
|
|
|
|
BroadcastLocalizedMessage(class'TSCMessages', 241);
|
|
}
|
|
|
|
function SetTeamCaptain(byte TeamIndex, PlayerReplicationInfo NewCaptainPRI)
|
|
{
|
|
if ( TeamIndex > 1 )
|
|
return;
|
|
if ( NewCaptainPRI != none && (NewCaptainPRI.Team == none || NewCaptainPRI.Team.TeamIndex != TeamIndex) )
|
|
return;
|
|
|
|
TSCGRI.TeamCaptain[TeamIndex] = NewCaptainPRI;
|
|
if ( NewCaptainPRI == none )
|
|
TSCVotingOptions.VotingHandler.TeamCaptains[TeamIndex] = none;
|
|
else
|
|
TSCVotingOptions.VotingHandler.TeamCaptains[TeamIndex] = PlayerController(NewCaptainPRI.Owner);
|
|
}
|
|
|
|
function bool ShouldKillOnTeamChange(Pawn TeamChanger)
|
|
{
|
|
if ( bTeamChanging )
|
|
return false;
|
|
|
|
// do not kill pawn, if changing to a smaller team
|
|
if ( !bWaveInProgress && TeamChanger.PlayerReplicationInfo != none
|
|
&& TeamChanger.PlayerReplicationInfo.Team != none
|
|
&& TeamChanger.PlayerReplicationInfo.Team.TeamIndex < 2
|
|
&& TeamChanger.PlayerReplicationInfo.Team.Size <= Teams[1-TeamChanger.PlayerReplicationInfo.Team.TeamIndex].Size )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function int CalcStartingCashBonus(PlayerController PC)
|
|
{
|
|
local int result;
|
|
|
|
result = super.CalcStartingCashBonus(PC);
|
|
if ( WaveNum > 0 && LateJoinerCashBonus > 0
|
|
&& !PC.PlayerReplicationInfo.bOnlySpectator
|
|
&& PC.PlayerReplicationInfo.Team != none
|
|
&& PC.PlayerReplicationInfo.Team.TeamIndex < 2
|
|
&& PC.PlayerReplicationInfo.Team.Size <= BigTeamSize
|
|
&& PC.PlayerReplicationInfo.Team.Size <= Teams[1-PC.PlayerReplicationInfo.Team.TeamIndex].Size
|
|
)
|
|
{
|
|
result += WaveNum * LateJoinerCashBonus/OriginalFinalWave * (1.0 - CurrentTeamMoneyPenalty);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function bool BecomeSpectator(PlayerController P)
|
|
{
|
|
if ( super.BecomeSpectator(P) ) {
|
|
// check if player is a team captain
|
|
if ( P.PlayerReplicationInfo.Team != none && P.PlayerReplicationInfo.Team.TeamIndex < 2 ) {
|
|
if ( TSCGRI.TeamCaptain[P.PlayerReplicationInfo.Team.TeamIndex] == P.PlayerReplicationInfo ) {
|
|
SetTeamCaptain(P.PlayerReplicationInfo.Team.TeamIndex, none);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function SetHumanDamage(EHumanDamageMode Hdmg)
|
|
{
|
|
HumanDamageMode = Hdmg;
|
|
TSCGRI.HumanDamageMode = Hdmg;
|
|
}
|
|
|
|
/**
|
|
* Returns true, if friendly fire is disabled. To be precise, FFDisabled() always returns true,
|
|
* if Victim can not be hurt by other player, even from other team.
|
|
*
|
|
* @param instigatedBy player pawn, which made damage to Victim
|
|
* @param Victim player pawn, which is hurt by instigatedBy
|
|
* @return true, if Victim shouldn't take damage from other player
|
|
*/
|
|
function bool FFDisabled(pawn instigatedBy, pawn Victim)
|
|
{
|
|
if ( bTradingDoorsOpen || WaveNum == 0 || HumanDamageMode == HDMG_None
|
|
|| (!bWaveBossInProgress && TotalMaxMonsters<=0 && NumMonsters < 10) )
|
|
return true;
|
|
|
|
if ( HumanDamageMode == HDMG_All )
|
|
return false;
|
|
|
|
if ( instigatedBy.GetTeamNum() == Victim.GetTeamNum() ) {
|
|
return HumanDamageMode < HDMG_Normal || TSCGRI.AtOwnBase(Victim);
|
|
}
|
|
return HumanDamageMode < HDMG_PvP && TSCGRI.AtOwnBase(Victim);
|
|
}
|
|
|
|
|
|
function int ReduceDamage(int Damage, pawn injured, pawn instigatedBy, vector HitLocation, out vector Momentum, class<DamageType> DamageType)
|
|
{
|
|
if ( Damage == 0 )
|
|
return 0;
|
|
|
|
if( instigatedBy!=injured && KFHumanPawn(injured) != none && instigatedBy != none && MonsterController(InstigatedBy.Controller)==None ) {
|
|
// player to player damage
|
|
if ( FFDisabled(instigatedBy, injured) )
|
|
return 0;
|
|
|
|
// same team friendly fire damage will be reduced in KFGameType
|
|
if ( instigatedBy.GetTeamNum()!=injured.GetTeamNum() )
|
|
Damage = max(1, round( Damage * FriendlyFireScale ) );
|
|
}
|
|
return super.ReduceDamage(Damage, injured, instigatedBy, HitLocation, Momentum, DamageType);
|
|
}
|
|
|
|
function Killed(Controller Killer, Controller Killed, Pawn KilledPawn, class<DamageType> damageType)
|
|
{
|
|
local TeamInfo KilledTeam;
|
|
local bool bHadMonsters;
|
|
local byte Ping; // ping is multiplied by 4 in KF!
|
|
local int HealthBeforeDeath;
|
|
local bool bSuicide;
|
|
|
|
bHadMonsters = NumMonsters > 0;
|
|
// save team here in cases when Killed destroyed by parent function
|
|
if ( Killed.bIsPlayer && Killed.PlayerReplicationInfo != none ) {
|
|
KilledTeam = Killed.PlayerReplicationInfo.Team;
|
|
Ping = Killed.PlayerReplicationInfo.Ping;
|
|
bSuicide = Killer == Killed && (damageType == class'Suicided' || damageType == class'DamageType');
|
|
if ( ScrnHumanPawn(KilledPawn) != none )
|
|
HealthBeforeDeath = ScrnHumanPawn(KilledPawn).HealthBeforeDeath;
|
|
}
|
|
|
|
super.Killed(Killer, Killed, KilledPawn, damageType);
|
|
|
|
|
|
if ( KilledTeam != none ) {
|
|
AliveTeamPlayerCount[KilledTeam.TeamIndex]--;
|
|
//Broadcast(Self, "HealthBeforeDeath="$HealthBeforeDeath);
|
|
// During Sudden Death wipe team after player's death, unless:
|
|
// - he isn't fully joined yet or lost the connection (ping == 255)
|
|
// - he isn't suicided/disconnected. To prevent cheating, only players with full health are allowed to suicide
|
|
if ( bWaveInProgress && Ping < 255 && damageType != class'DamTypeSudDeath'
|
|
&& (!bSuicide || HealthBeforeDeath < 80 ) )
|
|
{
|
|
TSCTeam(KilledTeam).Deaths++;
|
|
if ( TSCGRI.bSuddenDeath )
|
|
WipeTeam(KilledTeam);
|
|
}
|
|
|
|
if ( !bTeamWiped && AliveTeamPlayerCount[KilledTeam.TeamIndex] == 0) {
|
|
bTeamWiped = true;
|
|
// lower amount of remaining but not-spawned zeds twice
|
|
if ( TotalMaxMonsters > 1) {
|
|
TotalMaxMonsters /= 2;
|
|
TSCGRI.MaxMonsters = Max(TotalMaxMonsters + NumMonsters,0);
|
|
}
|
|
BroadcastLocalizedMessage(class'TSCMessages', 10+KilledTeam.TeamIndex*100);
|
|
TeamBases[KilledTeam.TeamIndex].SendHome();
|
|
|
|
}
|
|
}
|
|
else if ( NumMonsters == 0 && bHadMonsters && !bSingleTeam && damageType != class'Suicided' ) {
|
|
if ( AliveTeamPlayerCount[0] == 0 ^^ AliveTeamPlayerCount[1] == 0 )
|
|
DramaticEvent(1.0); // do ZED time on winner's kill
|
|
}
|
|
}
|
|
|
|
function ScoreKill(Controller Killer, Controller Other)
|
|
{
|
|
super.ScoreKill(Killer, Other);
|
|
|
|
if ( KFMonsterController(Other) != none && Killer != none && Killer.PlayerReplicationInfo != None && TSCTeam(killer.PlayerReplicationInfo.Team) != none ) {
|
|
TSCTeam(Killer.PlayerReplicationInfo.Team).ZedKills++;
|
|
}
|
|
}
|
|
|
|
function WipeTeam(TeamInfo Team, optional class<DamageType> DamageType)
|
|
{
|
|
local Controller C;
|
|
local Vector NullVector;
|
|
|
|
if ( DamageType == none )
|
|
DamageType = class'DamTypeSudDeath';
|
|
for ( C = Level.ControllerList; C != none; C = C.nextController ) {
|
|
if ( C.bIsPlayer && C.Pawn != none && C.Pawn.Health > 0
|
|
&& C.PlayerReplicationInfo != none && C.PlayerReplicationInfo.Team == Team )
|
|
{
|
|
C.Pawn.Died(none, DamageType, NullVector);
|
|
}
|
|
}
|
|
}
|
|
|
|
function bool CheckEndGame(PlayerReplicationInfo Winner, string Reason)
|
|
{
|
|
local Controller P;
|
|
local PlayerController Player;
|
|
local bool bSetAchievement;
|
|
local string MapName;
|
|
local String EndSong;
|
|
|
|
if ( Reason == "TeamScoreLimit" ) {
|
|
if ( AliveTeamPlayerCount[0] > 0 && AliveTeamPlayerCount[1] == 0 ) {
|
|
TSCGRI.Winner = Teams[0];
|
|
EndSong = SongRedWin;
|
|
}
|
|
else if ( AliveTeamPlayerCount[0] == 0 && AliveTeamPlayerCount[1] > 0 ) {
|
|
TSCGRI.Winner = Teams[1];
|
|
EndSong = SongBlueWin;
|
|
}
|
|
else
|
|
return false;
|
|
TSCGRI.EndGameType = 2;
|
|
ScrnBalanceMut.BroadcastMessage(TeamInfo(TSCGRI.Winner).GetHumanReadableName() $ " team won the game on wave " $ string(WaveNum+1), true);
|
|
}
|
|
else {
|
|
if ( WaveNum >= OriginalFinalWave + OvertimeWaves + SudDeathWaves ) {
|
|
TSCGRI.Winner = none;
|
|
TSCGRI.EndGameType = 2;
|
|
EndSong = SongBothWin;
|
|
|
|
if ( GameDifficulty >= 2.0 )
|
|
{
|
|
bSetAchievement = true;
|
|
|
|
// Get the MapName out of the URL
|
|
MapName = GetCurrentMapName(Level);
|
|
}
|
|
}
|
|
else {
|
|
TSCGRI.EndGameType = 1;
|
|
EndSong = SongBothWiped;
|
|
}
|
|
}
|
|
|
|
if ( (GameRulesModifiers != None) && !GameRulesModifiers.CheckEndGame(Winner, Reason) ) {
|
|
TSCGRI.EndGameType = 0;
|
|
TSCGRI.Winner = none;
|
|
return false;
|
|
}
|
|
|
|
// if we reached here, game must be ended
|
|
EndTime = Level.TimeSeconds + EndTimeDelay;
|
|
|
|
for ( P = Level.ControllerList; P != none; P = P.nextController )
|
|
{
|
|
Player = PlayerController(P);
|
|
if ( Player != none ) {
|
|
Player.ClientSetBehindView(true);
|
|
Player.ClientGameEnded();
|
|
|
|
if ( bSetAchievement && Player.PlayerReplicationInfo != none && KFSteamStatsAndAchievements(Player.SteamStatsAndAchievements) != none
|
|
&& (Player.PlayerReplicationInfo.Team == GameReplicationInfo.Winner || GameReplicationInfo.Winner == none) )
|
|
{
|
|
KFSteamStatsAndAchievements(Player.SteamStatsAndAchievements).WonGame(MapName, GameDifficulty, KFGameLength == GL_Long);
|
|
}
|
|
|
|
if (KFPlayerController(Player)!= none)
|
|
KFPlayerController(Player).NetPlayMusic(EndSong, 0.5, 0);
|
|
}
|
|
|
|
P.GameHasEnded();
|
|
}
|
|
|
|
if ( CurrentGameProfile != none )
|
|
CurrentGameProfile.bWonMatch = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
function TSCBaseGuardian SpawnBaseGuardian(byte TeamIndex)
|
|
{
|
|
local NavigationPoint N;
|
|
local TSCBaseGuardian gnome;
|
|
|
|
if ( TeamIndex >= 2 )
|
|
return none;
|
|
|
|
if ( TeamBases[TeamIndex] != none )
|
|
return TeamBases[TeamIndex]; // already spawned
|
|
|
|
gnome = spawn(BaseGuardianClasses[TeamIndex], TSCGRI);
|
|
if ( gnome == none ) {
|
|
// if unable to spawn at zero point, try player spawn
|
|
N = FindPlayerStart(none, TeamIndex);
|
|
if ( N != none )
|
|
gnome = spawn(BaseGuardianClasses[TeamIndex], TSCGRI, '', N.Location);
|
|
}
|
|
if ( gnome == none ) {
|
|
// realy? can't spawn gnome at player spawn?!
|
|
N = Level.NavigationPointList;
|
|
while ( gnome == none && N != none ) {
|
|
gnome = spawn(BaseGuardianClasses[TeamIndex], TSCGRI, '', N.Location);
|
|
N = N.nextNavigationPoint;
|
|
}
|
|
}
|
|
if ( gnome == none ) {
|
|
// map is totally fucked up
|
|
log("Unable to spawn Base Guardian for team " $ TeamIndex, 'TSC');
|
|
return none;
|
|
}
|
|
|
|
gnome.Team = Teams[TeamIndex];
|
|
gnome.TSCGRI = TSCGRI;
|
|
TeamBases[TeamIndex] = gnome;
|
|
|
|
return gnome;
|
|
}
|
|
|
|
// balances the team referenced by SmallTeamIndex
|
|
// BalanceMult shows the relatice amount of bonus that must be given to small team
|
|
// Usually BalanceMult=BigTeam.Size / SmallTeam.Size
|
|
// callint BalanceTeam(2, 1.0) removes any previously given bonuses
|
|
function BalanceTeams(byte SmallTeamIndex, float BalanceMult)
|
|
{
|
|
local Controller C;
|
|
local Inventory Inv;
|
|
local Syringe S;
|
|
local ScrnHumanPawn ScrnPawn;
|
|
local bool bInSmallTeam;
|
|
|
|
if ( bSingleTeamGame )
|
|
return;
|
|
|
|
for ( C = Level.ControllerList; C != none; C = C.nextController ) {
|
|
if ( !C.bIsPlayer || C.PlayerReplicationInfo == none || C.Pawn == none )
|
|
continue;
|
|
ScrnPawn = ScrnHumanPawn(C.Pawn);
|
|
if ( ScrnPawn == none )
|
|
continue;
|
|
|
|
bInSmallTeam = C.PlayerReplicationInfo.Team != none
|
|
&& C.PlayerReplicationInfo.Team.TeamIndex == SmallTeamIndex;
|
|
ScrnPawn.HealthBonus = 0;
|
|
if ( bInSmallTeam )
|
|
ScrnPawn.HealthBonus *= BalanceMult; // increases max health
|
|
ScrnPawn.GiveHealth(0, 100); // updates max health
|
|
for ( Inv = ScrnPawn.Inventory; Inv != none; Inv = Inv.Inventory ) {
|
|
S = Syringe(Inv);
|
|
if ( S != none ) {
|
|
S.HealBoostAmount = S.default.HealBoostAmount;
|
|
// give more healing points to smaller team
|
|
if ( bInSmallTeam )
|
|
S.HealBoostAmount *= BalanceMult;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns wave number relative to the current game length
|
|
function byte RelativeWaveNum(float LongGameWaveNum)
|
|
{
|
|
if ( OriginalFinalWave == 10 )
|
|
return ceil(LongGameWaveNum);
|
|
return ceil(LongGameWaveNum * OriginalFinalWave / 10.0);
|
|
}
|
|
|
|
function SetupWave()
|
|
{
|
|
local int i;
|
|
|
|
bWaveInProgress = true;
|
|
TSCGRI.bWaveInProgress = true;
|
|
|
|
if ( (WaveNum+1) == RelativeWaveNum(ScrnBalanceMut.LockTeamAutoWave) )
|
|
LockTeams();
|
|
|
|
NextMonsterTime = Level.TimeSeconds + 5.0;
|
|
TraderProblemLevel = 0;
|
|
rewardFlag=false;
|
|
ZombiesKilled=0;
|
|
WaveMonsters = 0;
|
|
WaveNumClasses = 0;
|
|
bWaveEnding = false;
|
|
|
|
SetupPickups();
|
|
|
|
i = rand(2);
|
|
TeamBases[i].ScoreOrHome();
|
|
TeamBases[1-i].ScoreOrHome();
|
|
|
|
WavePlayerCount = AlivePlayerCount;
|
|
BigTeamSize = max(AliveTeamPlayerCount[0], AliveTeamPlayerCount[1]);
|
|
SmallTeamSize = min(AliveTeamPlayerCount[0], AliveTeamPlayerCount[1]);
|
|
bSingleTeam = AliveTeamPlayerCount[0] == 0 || AliveTeamPlayerCount[1] == 0;
|
|
bTeamWiped = bSingleTeam;
|
|
|
|
if ( bSingleTeam || AliveTeamPlayerCount[0] == AliveTeamPlayerCount[1] ) {
|
|
BalanceTeams(2, 1.0); // remove bonuses
|
|
SmallTeamSize = BigTeamSize;
|
|
}
|
|
else if ( AliveTeamPlayerCount[0] < AliveTeamPlayerCount[1] )
|
|
BalanceTeams(0, float(AliveTeamPlayerCount[1])/AliveTeamPlayerCount[0]);
|
|
else if ( AliveTeamPlayerCount[0] > AliveTeamPlayerCount[1] )
|
|
BalanceTeams(1, float(AliveTeamPlayerCount[0])/AliveTeamPlayerCount[1]);
|
|
|
|
ScrnGameLength.RunWave();
|
|
|
|
CalcDoshDifficultyMult();
|
|
|
|
if( WaveNum == FinalWave && bUseEndGameBoss ) {
|
|
StartWaveBoss();
|
|
return;
|
|
}
|
|
|
|
if ( WaveNum == 0 ) {
|
|
BroadcastLocalizedMessage(class'TSCMessages', 230); // human damage disabled
|
|
}
|
|
else if ( WaveNum >= OriginalFinalWave ) {
|
|
if ( WaveNum >= OriginalFinalWave + OvertimeWaves ) {
|
|
TSCGRI.bSuddenDeath = true;
|
|
BroadcastLocalizedMessage(class'TSCMessages', 302); // sudden death
|
|
// if one of the team do not have a base - wipe it
|
|
}
|
|
else {
|
|
BroadcastLocalizedMessage(class'TSCMessages', 201); // overtime
|
|
// lock teams one wave before Sudden Death
|
|
if ( bLockTeamsOnSuddenDeath && SudDeathWaves > 0 && WaveNum+1 == OriginalFinalWave + OvertimeWaves )
|
|
LockTeams();
|
|
}
|
|
}
|
|
else if ( HumanDamageMode > HDMG_None ) {
|
|
if ( HumanDamageMode >= HDMG_Normal )
|
|
BroadcastLocalizedMessage(class'TSCMessages', 231); // human damage enabled
|
|
else
|
|
BroadcastLocalizedMessage(class'TSCMessages', 232); // enemy fire enabled
|
|
}
|
|
|
|
TotalMaxMonsters = ScrnGameLength.GetWaveZedCount() + NumMonsters;
|
|
WaveEndTime = ScrnGameLength.GetWaveEndTime();
|
|
AdjustedDifficulty = GameDifficulty + lerp(float(WaveNum)/FinalWave, 0.1, 0.3);
|
|
|
|
MaxMonsters = min(TotalMaxMonsters, MaxZombiesOnce); // max monsters that can be spawned
|
|
TSCGRI.MaxMonsters = TotalMaxMonsters; // num monsters in wave replicated to clients
|
|
TSCGRI.MaxMonstersOn = true; // I've no idea what is this for
|
|
|
|
NextSquadTeam = rand(2); // pickup random team for the next special squad
|
|
for( i=0; i<ZedSpawnList.Length; ++i )
|
|
ZedSpawnList[i].Reset();
|
|
|
|
//Now build the first squad to use
|
|
SquadsToUse.Length = 0; // force BuildNextSquad() to rebuild squad list
|
|
SpecialListCounter = 0;
|
|
BuildNextSquad();
|
|
|
|
TSCTeams[0].WaveKills = TSCTeams[0].ZedKills;
|
|
TSCTeams[1].WaveKills = TSCTeams[1].ZedKills;
|
|
TSCTeams[0].PrevMinKills = TSCTeams[0].ZedKills;
|
|
TSCTeams[1].PrevMinKills = TSCTeams[1].ZedKills;
|
|
TSCTeams[0].LastMinKills = TSCTeams[0].ZedKills;
|
|
TSCTeams[1].LastMinKills = TSCTeams[1].ZedKills;
|
|
}
|
|
|
|
function bool AddSquad()
|
|
{
|
|
if ( NextSpawnSquad.length == 0 ) {
|
|
NextSquadTeam = 1 - NextSquadTeam;
|
|
LastZVol = none;
|
|
|
|
if ( PendingSpecialSquad.length != 0 ) {
|
|
// spawn the same special squad for another team
|
|
NextSpawnSquad = PendingSpecialSquad;
|
|
PendingSpecialSquad.length = 0;
|
|
}
|
|
else {
|
|
ScrnGameLength.LoadNextSpawnSquad(NextSpawnSquad);
|
|
if ( NextSpawnSquad.length == 0 )
|
|
return false;
|
|
|
|
if ( ScrnGameLength.bLoadedSpecial ) {
|
|
if (!bTeamWiped)
|
|
PendingSpecialSquad = NextSpawnSquad; // backup for another team
|
|
bCheckSquadTeam = true; // One special squad per each team
|
|
ZedSpawnLoc = ZSLOC_CLOSER; // spawn close to the designated team
|
|
}
|
|
else {
|
|
// Check teams only when they are equal in number.
|
|
// If teams are uneven, then just pick up random player as squad's target
|
|
bCheckSquadTeam = BigTeamSize == SmallTeamSize;
|
|
ZedSpawnLoc = ZSLOC_RANDOM; // make zeds spawn more random on the map
|
|
}
|
|
}
|
|
}
|
|
return super.AddSquad();
|
|
}
|
|
|
|
// returns random alive player
|
|
function Controller FindSquadTarget()
|
|
{
|
|
local array<Controller> CL;
|
|
local Controller C;
|
|
|
|
for( C=Level.ControllerList; C!=None; C=C.NextController ) {
|
|
if ( C.bIsPlayer && C.Pawn!=None && C.Pawn.Health>0
|
|
&& (bTeamWiped || !bCheckSquadTeam || C.GetTeamNum() == NextSquadTeam) )
|
|
CL[CL.Length] = C;
|
|
}
|
|
if( CL.Length>0 )
|
|
return CL[Rand(CL.Length)];
|
|
|
|
return super.FindSquadTarget(); // in case when there are no team players alive
|
|
}
|
|
|
|
protected function StartTourney()
|
|
{
|
|
super.StartTourney();
|
|
|
|
GameReplicationInfo.bNoTeamSkins = bSingleTeamGame;
|
|
|
|
OvertimeTeamMoneyPenalty = 0.50;
|
|
LateJoinerCashBonus = 0;
|
|
bAntiBlocker = true;
|
|
bVoteHDmg = false;
|
|
}
|
|
|
|
function bool RewardSurvivingPlayers()
|
|
{
|
|
// At this WaveNum isn't increased yet
|
|
if ( OvertimeTeamMoneyPenalty > 0 && WaveNum+1 >= OriginalFinalWave ) {
|
|
CurrentTeamMoneyPenalty = fmin(CurrentTeamMoneyPenalty + OvertimeTeamMoneyPenalty, 1);
|
|
Teams[0].Score = int(Teams[0].Score * (1.0 - CurrentTeamMoneyPenalty));
|
|
Teams[1].Score = int(Teams[1].Score * (1.0 - CurrentTeamMoneyPenalty));
|
|
Broadcast(Self, strBudgetCut @ int(100*(CurrentTeamMoneyPenalty)) $ "%");
|
|
}
|
|
|
|
return super.RewardSurvivingPlayers();
|
|
}
|
|
|
|
function ShowPathTo(PlayerController P, int DestinationIndex)
|
|
{
|
|
if ( DestinationIndex == 1 )
|
|
ShowPathToBase(P);
|
|
else
|
|
super.ShowPathTo(P, DestinationIndex); // show path to shop
|
|
}
|
|
|
|
function ShowPathToBase(PlayerController P)
|
|
{
|
|
local TSCBaseGuardian gnome;
|
|
local byte TeamNum;
|
|
|
|
TeamNum = P.PlayerReplicationInfo.Team.TeamIndex;
|
|
gnome = TeamBases[TeamNum];
|
|
if ( gnome == none || !gnome.bActive || TSCGRI.AtOwnBase(P.Pawn) )
|
|
{
|
|
ScrnPlayerController(P).ServerShowPathTo(255); // turn off
|
|
return;
|
|
}
|
|
|
|
if ( P.FindPathToward(gnome, false) != None ) {
|
|
Spawn(BaseWhisp, P,, P.Pawn.Location);
|
|
}
|
|
}
|
|
|
|
function ShopVolume TeamShop(byte TeamIndex)
|
|
{
|
|
switch (TeamIndex) {
|
|
case 0: return TSCGRI.CurrentShop;
|
|
case 1: return TSCGRI.BlueShop;
|
|
}
|
|
return none;
|
|
}
|
|
|
|
State MatchInProgress
|
|
{
|
|
function WaveTimer()
|
|
{
|
|
super.WaveTimer();
|
|
|
|
if ( TotalMaxMonsters <= 0 ) {
|
|
if ( NumMonsters > 0 && NumMonsters < 10 ) {
|
|
if ( bWaveEnding ) {
|
|
if ( --WaveEndingCountDown <= 0) {
|
|
KillRemainingZeds(true);
|
|
bWaveEnding = false;
|
|
}
|
|
// tell about disabling Human Damage 3 seconds after auto-end message
|
|
if ( HumanDamageMode > HDMG_None && WaveEndingCountDown == default.WaveEndingCountDown-3 )
|
|
BroadcastLocalizedMessage(class'TSCMessages', 230);
|
|
}
|
|
else {
|
|
bWaveEnding = true;
|
|
WaveEndingCountDown = default.WaveEndingCountDown; // force end wave in 30 seconds
|
|
BroadcastLocalizedMessage(class'TSCMessages', 200); // tell about auto-end
|
|
}
|
|
}
|
|
}
|
|
if ( (int(WaveTimeElapsed)%60) == 0 ) {
|
|
TSCTeams[0].PrevMinKills = TSCTeams[0].LastMinKills;
|
|
TSCTeams[0].LastMinKills = TSCTeams[0].ZedKills;
|
|
TSCTeams[1].PrevMinKills = TSCTeams[1].LastMinKills;
|
|
TSCTeams[1].LastMinKills = TSCTeams[1].ZedKills;
|
|
}
|
|
}
|
|
|
|
function DoWaveEnd()
|
|
{
|
|
local int NextWave;
|
|
|
|
NextWave = WaveNum + 1;
|
|
if ( !bSingleTeam && (AliveTeamPlayerCount[0] == 0 ^^ AliveTeamPlayerCount[1] == 0) ) {
|
|
EndGame(None,"TeamScoreLimit");
|
|
}
|
|
else if ( AlivePlayerCount > 0 && NextWave >= OriginalFinalWave ) {
|
|
if ( NextWave >= OriginalFinalWave + OvertimeWaves + SudDeathWaves ) {
|
|
WaveNum++;
|
|
EndGame(None,"TimeLimit");
|
|
return;
|
|
}
|
|
if ( OvertimeWaves > 0 && NextWave == OriginalFinalWave ) {
|
|
BroadcastLocalizedMessage(class'TSCMessages', 201);
|
|
}
|
|
else if ( NextWave >= OriginalFinalWave + OvertimeWaves ) {
|
|
TSCGRI.bSuddenDeath = true;
|
|
BroadcastLocalizedMessage(class'TSCMessages', 202);
|
|
}
|
|
FinalWave++;
|
|
}
|
|
|
|
if ( !bSingleTeamGame ) {
|
|
if ( TeamBases[0] == none )
|
|
SpawnBaseGuardian(0); // just in case
|
|
// don't check for none here making warning message appear in server log
|
|
// to point on indicated bad map design
|
|
TeamBases[0].SendHome();
|
|
}
|
|
if ( TeamBases[1] == none )
|
|
SpawnBaseGuardian(1);
|
|
TeamBases[1].SendHome();
|
|
|
|
if ( ScrnGameLength != none && NextWave >= ScrnGameLength.Waves.length ) {
|
|
// if there are not enough waves in ScrnGameLength, then just re-load the last one again
|
|
WaveNum = ScrnGameLength.Waves.length - 2;
|
|
}
|
|
super.DoWaveEnd();
|
|
if ( !bGameEnded ) {
|
|
WaveNum = NextWave;
|
|
}
|
|
TSCGRI.WaveNumber = WaveNum;
|
|
|
|
if ( bPendingShuffle ) {
|
|
ShuffleTeams();
|
|
WaveCountDown += 30;
|
|
TSCGRI.TimeToNextWave = WaveCountDown;
|
|
}
|
|
}
|
|
|
|
function SelectShop()
|
|
{
|
|
local array<ShopVolume> TempShopList;
|
|
local int i, t;
|
|
|
|
// Can't select a shop if there aren't any
|
|
if ( ShopList.Length == 0 )
|
|
return;
|
|
|
|
for ( i = 0; i < ShopList.Length; i++ ) {
|
|
if ( !ShopList[i].bAlwaysClosed )
|
|
TempShopList[TempShopList.Length] = ShopList[i];
|
|
}
|
|
if ( TempShopList.Length == 1 ) {
|
|
// just to avoid deadends, but TSC games shouldn't be played on maps with only 1 shop
|
|
TSCGRI.CurrentShop = TempShopList[0];
|
|
TSCGRI.BlueShop = TempShopList[0];
|
|
return;
|
|
}
|
|
|
|
// select random shop for random team
|
|
i = Rand(TempShopList.Length);
|
|
if ( TempShopList[i] == TSCGRI.CurrentShop )
|
|
t = 1; // can't be the same shop twice for the red team, so set it for blue
|
|
else if ( TempShopList[i] == TSCGRI.BlueShop )
|
|
t = 0; // can't be the same shop twice for the blue team, so set it for red
|
|
else
|
|
t = rand(2); // set shop for random team
|
|
TeamShops[t] = TempShopList[i];
|
|
TempShopList.remove(i, 1);
|
|
|
|
// select shop for the other team
|
|
t = 1 - t;
|
|
i = Rand(TempShopList.Length);
|
|
if ( TempShopList[i] == TSCGRI.GetTeamShop(t) ) {
|
|
// can't use the same shop twice
|
|
TempShopList.remove(i, 1);
|
|
i = Rand(TempShopList.Length);
|
|
}
|
|
TeamShops[t] = TempShopList[i];
|
|
|
|
// write shops to GRI
|
|
TSCGRI.CurrentShop = TeamShops[0];
|
|
TSCGRI.BlueShop = TeamShops[1];
|
|
}
|
|
|
|
function OpenShops()
|
|
{
|
|
local int i;
|
|
local Controller C;
|
|
|
|
bTradingDoorsOpen = True;
|
|
|
|
for( i=0; i<ShopList.Length; i++ ) {
|
|
if( !ShopList[i].bAlwaysClosed && ShopList[i].bAlwaysEnabled )
|
|
ShopList[i].OpenShop();
|
|
}
|
|
|
|
if ( TSCGRI.CurrentShop == none || TSCGRI.BlueShop == none )
|
|
SelectShop();
|
|
|
|
if ( !bSingleTeamGame )
|
|
TSCGRI.CurrentShop.OpenShop();
|
|
TSCGRI.BlueShop.OpenShop();
|
|
|
|
if ( !bNoBases ) {
|
|
if ( !bSingleTeamGame )
|
|
TeamBases[0].MoveToShop(TSCGRI.CurrentShop);
|
|
TeamBases[1].MoveToShop(TSCGRI.BlueShop);
|
|
}
|
|
|
|
// Tell all players to start showing the path to the trader
|
|
For( C=Level.ControllerList; C!=None; C=C.NextController )
|
|
{
|
|
if( C.Pawn!=None && C.Pawn.Health>0 )
|
|
{
|
|
// Disable pawn collision during trader time
|
|
C.Pawn.bBlockActors = !bAntiBlocker;
|
|
|
|
if( KFPlayerController(C) !=None )
|
|
{
|
|
KFPlayerController(C).SetShowPathToTrader(true);
|
|
KFPlayerController(C).ClientLocationalVoiceMessage(C.PlayerReplicationInfo, none, 'TRADER', 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// function float GetMinSpawnDelay()
|
|
// {
|
|
// local float result;
|
|
|
|
// result = super.GetMinSpawnDelay();
|
|
// if ( !bTeamWiped )
|
|
// result *= 0.5; // up to twice faster spawns
|
|
// return result;
|
|
// }
|
|
} //MatchInProgress
|
|
|
|
defaultproperties
|
|
{
|
|
RedTeamHumanName="British"
|
|
BlueTeamHumanName="Steampunk"
|
|
OvertimeWaves=2
|
|
SudDeathWaves=1
|
|
OvertimeTeamMoneyPenalty=0.330000
|
|
LateJoinerCashBonus=700
|
|
SongRedWin="KF_WPrevention"
|
|
SongBlueWin="KFh6_Launch"
|
|
SongBothWin="KF_Containment"
|
|
SongBothWiped="KF_Hunger"
|
|
strBudgetCut="Team wallets were cut off by"
|
|
BaseRadius=1250.000000
|
|
MinBaseZ=-60.000000
|
|
MaxBaseZ=200.000000
|
|
WaveEndingCountdown=30
|
|
BaseGuardianClasses(0)=Class'ScrnBalanceSrv.TSCGuardianRed'
|
|
BaseGuardianClasses(1)=Class'ScrnBalanceSrv.TSCGuardianBlue'
|
|
BaseWhisp=Class'ScrnBalanceSrv.GreenWhisp'
|
|
bLockTeamsOnSuddenDeath=True
|
|
ClanTags(0)=(Prefix="[",Postfix="]")
|
|
ClanTags(1)=(Prefix="(",Postfix=")")
|
|
ClanTags(2)=(Prefix="-=",Postfix="=-")
|
|
ClanTags(3)=(Prefix="{",Postfix="}")
|
|
ClanTags(4)=(Prefix="<",Postfix=">")
|
|
ClanTags(5)=(Prefix=".:",Postfix=":.")
|
|
ClanTags(6)=(Prefix="*",Postfix="*")
|
|
ClanTags(7)=(Postfix="-")
|
|
ClanTags(8)=(Prefix=".")
|
|
bClanCheck=True
|
|
HumanDamageMode=HDMG_Normal
|
|
bVoteHDmg=True
|
|
HDmgScale=0.100000
|
|
bSingleTeamGame=False
|
|
KFGameLength=1
|
|
bUseEndGameBoss=False
|
|
StandardMaxZombiesOnce=48
|
|
KFHints(0)="Each team has own Trader. Team can not get the same Trader two times in a row."
|
|
KFHints(1)="When the Trader opens her doors, she drops Base Guardian nearby. Take it to your base!"
|
|
KFHints(2)="Pick up a Base Guardian next to the Trader, bring it where you want your Base to be and press SETUPBASE(ALTFIRE) button."
|
|
KFHints(3)="Base can be established only once per wave. Once set up, it can not be moved."
|
|
KFHints(4)="If nobody stays at a Base, then the Guardian gets frustrated and disappears. No Guardian = No Base."
|
|
KFHints(5)="Base Guardian has 2 cool features: it protects you from the Friendly Fire and damages enemy squad members."
|
|
KFHints(6)="Base Guardian hurts enemy players withing the range of the Base no matter of Friendly Fire setting."
|
|
KFHints(7)="Base can be established during the Trader Time only. So hurry up!"
|
|
KFHints(8)="Other squad cannot stay at your Base."
|
|
KFHints(9)="You can play without a Base too, but who is going to protect you from the Friendly Fire then?.."
|
|
KFHints(10)="Setting up a Base in a strategical map point is a key to success."
|
|
KFHints(11)="You can not set up own Base at Enemy Base. However, Bases may intersect."
|
|
KFHints(12)="While carrying Base Guardian you can pass it to other player by pressing the same key as throwing a weapon."
|
|
KFHints(13)="Best camping spots in standard KF game are not necessarily the best spots in TSC."
|
|
KFHints(14)="Team gets wiped if ANY its member dies during SUDDEN DEATH wave."
|
|
KFHints(15)="Wiping enemy squad doesn't grant you a win. You still have to survive till the end of the wave."
|
|
KFHints(16)="TSC isn't Versus or Team Deathmatch game. You don't have to fight the enemy team. Leave that job to ZEDs."
|
|
KFHints(17)="You can wait until ZEDs wipe out enemy squad or help them. Help who? ZEDs or the other squad? Thr choice is up to you..."
|
|
KFHints(18)="There are 4 Human Damage rules in TSC: OFF, No Friendly Fire, Normal and PvP. Type MVOTE TEAM HDMG for the info."
|
|
KFHints(19)="When Human Damage is OFF, you can not damage other squad's members. Your Guardian can though."
|
|
KFHints(20)="Human Damage is OFF during the first wave, Trader Time and when there are less than 10 zeds left in a wave."
|
|
KFHints(21)="Human Damage is ON during waves (except first one). Staying at own Base protects you from it."
|
|
KFHints(22)="You can switch team during the Trader Time. Type SWITCHTEAM in the console or bind it to key."
|
|
KFHints(23)="Switching the team during the game kills you first. Then restarts you as other squad's member."
|
|
KFHints(24)="You can not switch a team during a wave."
|
|
KFHints(25)="Medics and medics only can see health and armor of enemy players"
|
|
KFHints(26)="TSC, same as KF, is not about winning or losing. It is all about surviving."
|
|
KFHints(27)="TSC doesn't force you to play against the other squad. You can cooperate and survive together. But nobody said that the other squad thinks the same..."
|
|
KFHints(28)="During the Trader Time, if other squad has 2 players less than yours, you can switch to it on-the-fly (without dying)."
|
|
MaxZombiesOnce=48
|
|
FinalWave=7
|
|
FriendlyFireScale=0.100000
|
|
PathWhisps(1)="ScrnBalanceSrv.BlueWhisp"
|
|
MinNetPlayers=2
|
|
DefaultEnemyRosterClass="ScrnBalanceSrv.TSCTeam"
|
|
MaxPlayers=12
|
|
GameReplicationInfoClass=Class'ScrnBalanceSrv.TSCGameReplicationInfo'
|
|
GameName="Team Survival Competition"
|
|
Description="Two Teams, One Floor. Killing Floor. There are two teams competing in surviving specimen invasion on the same map and at the same time. Both teams can cooperate, fight against each other or just stay each in own corner of the map - choice is up to you..."
|
|
ScreenShotName="TSC_T.Team.TSC"
|
|
}
|