332 lines
10 KiB
Ucode
332 lines
10 KiB
Ucode
class FtgGame extends TSCGame
|
|
config;
|
|
|
|
var class<StinkyClot> StinkyClass;
|
|
var StinkyController StinkyControllers[2];
|
|
|
|
var transient float NextStinkySpawnTime;
|
|
|
|
event InitGame( string Options, out string Error )
|
|
{
|
|
// setting bCustomScoreboard=true tells TSCGame to do NOT set TSC Scoreboard
|
|
bCustomScoreboard = bSingleTeamGame;
|
|
super.InitGame(Options, Error);
|
|
HUDType = string(Class'FtgHUD');
|
|
|
|
if ( bSingleTeamGame ) {
|
|
FriendlyFireScale = 0;
|
|
bSingleTeam = true;
|
|
bTeamWiped = true;
|
|
bUseEndGameBoss = true;
|
|
OvertimeWaves = 1; // boss wave
|
|
SudDeathWaves = 0;
|
|
}
|
|
}
|
|
|
|
function SetupWave()
|
|
{
|
|
local byte t;
|
|
|
|
super.SetupWave();
|
|
|
|
if ( (ScrnGameLength != none && ScrnGameLength.Wave.bOpenTrader) || (ScrnGameLength == none && WaveNum > 0) ) {
|
|
ZedSpawnLoc = ZSLOC_CLOSER;
|
|
//TotalMaxMonsters *= 0.75; // reduce amount of zeds in wave
|
|
FriendlyFireScale = HDmgScale;
|
|
|
|
if ( bSingleTeamGame ) {
|
|
bSingleTeam = true;
|
|
bTeamWiped = true;
|
|
t = 1; // skip team 0
|
|
}
|
|
while ( t < 2 ) {
|
|
if ( !TeamBases[t].bActive ) {
|
|
// if players have lost base during the Trader Time
|
|
TeamBases[t].MoveToShop(TeamShops[t]);
|
|
TeamBases[t].ScoreOrHome();
|
|
}
|
|
++t;
|
|
}
|
|
NextStinkySpawnTime = Level.TimeSeconds + 10;
|
|
}
|
|
}
|
|
|
|
function Killed(Controller Killer, Controller Killed, Pawn KilledPawn, class<DamageType> damageType)
|
|
{
|
|
local byte t;
|
|
|
|
super.Killed(Killer, Killed, KilledPawn, damageType);
|
|
|
|
if ( TSCGRI.MaxMonsters == 9 ) {
|
|
// allow stinky clots to be killed at the end of the wave
|
|
for ( t = 0; t < 2; ++t ) {
|
|
if ( StinkyControllers[t] != none && StinkyControllers[t] != Killed && StinkyControllers[t].Pawn != none )
|
|
{
|
|
StinkyControllers[t].Pawn.SetCollision(true, true);
|
|
StinkyClot(StinkyControllers[t].Pawn).SetSkin();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function bool SpawnStinkyClot()
|
|
{
|
|
local int numspawned;
|
|
local ZombieVolume zvol;
|
|
local array< class<KFMonster> > StinkySquad;
|
|
local int i;
|
|
local int total;
|
|
|
|
while ( zvol == none && ++i < 5 )
|
|
{
|
|
zvol = ZedSpawnList[rand(ZedSpawnList.Length)];
|
|
if ( !zvol.bNormalZeds )
|
|
zvol = none; // unable to spawn clot in that zombie volume
|
|
}
|
|
if( zvol == none ) {
|
|
log("Couldn't find a place to spawn a Stinky Clot ("$StinkyClass$")! Trying again later.", class.name);
|
|
return false;
|
|
}
|
|
|
|
total = 32; // dummy value
|
|
StinkySquad[0] = StinkyClass;
|
|
if ( !bSingleTeamGame && StinkyControllers[0] == none && TeamBases[0].bActive
|
|
&& StinkyControllers[1] == none && TeamBases[1].bActive )
|
|
{
|
|
StinkySquad[1] = StinkyClass; // spawn two of them
|
|
}
|
|
if( zvol.SpawnInHere(StinkySquad,,numspawned,total,32,,true))
|
|
{
|
|
//NumMonsters+=numspawned;
|
|
//WaveMonsters+=numspawned;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
log("Failed to spawn the Stinky Clot: "$StinkyClass, class.name);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function byte GetTeamForStinkyController(StinkyController SC)
|
|
{
|
|
local byte t;
|
|
|
|
if ( bSingleTeamGame || StinkyControllers[0] != none || !TeamBases[0].bActive )
|
|
t = 1;
|
|
|
|
if ( StinkyControllers[t] != none || !TeamBases[t].bActive )
|
|
return 255; //kill me!!!
|
|
return t;
|
|
}
|
|
|
|
function StinkyControllerReady(StinkyController SC)
|
|
{
|
|
local byte t;
|
|
local int i;
|
|
local NavigationPoint N;
|
|
|
|
SC.TeamIndex = GetTeamForStinkyController(SC);
|
|
if ( SC.TeamIndex > 1 )
|
|
{
|
|
// wtf?
|
|
SC.GotoState('LatentDeath', 'Begin'); // can't kill pawn in its PostBeginPlay(). Need to wait a bit.
|
|
return;
|
|
}
|
|
|
|
if ( bWaveBossInProgress)
|
|
SelectShop(); // during boss wave select different move locations per each stinky clot
|
|
|
|
for ( t=0; t < 2; ++t ) {
|
|
if ( !TeamShops[t].bTelsInit )
|
|
TeamShops[t].InitTeleports();
|
|
if ( TeamShops[t].TelList.Length == 1 ) {
|
|
// from stupid maps with only 1 player teleport per trader
|
|
TeamShops[t].TelList[1] = TeamShops[t].TelList[0];
|
|
}
|
|
}
|
|
|
|
// TODO: add support to custom destination on the map.
|
|
// Until then StinkyClot will move to enemy shop as first target
|
|
SC.ActionNum = 0;
|
|
SC.MoveTargets[i++] = TeamBases[SC.TeamIndex];
|
|
if ( AmmoPickups.length >= 5 ) {
|
|
for ( t=0; t < 2; ++t ) {
|
|
// moving directly to ammo box is kinda bugged. So we are moving to closest path node instead
|
|
N = SC.FindClosestPathNode(AmmoPickups[rand(AmmoPickups.length)]);
|
|
if ( N != none )
|
|
SC.MoveTargets[i++] = N;
|
|
}
|
|
}
|
|
SC.MoveTargets[i++] = TeamShops[1-SC.TeamIndex].TelList[1-SC.TeamIndex];
|
|
SC.MoveTargets[i++] = TeamShops[SC.TeamIndex].TelList[SC.TeamIndex]; // move to own shop
|
|
SC.MoveTargets.length = i;
|
|
if ( bWaveBossInProgress ) {
|
|
SC.Pawn.SetCollision(true, true); // Stinky Clot allways can be killed during the boss wave
|
|
StinkyClot(StinkyControllers[t].Pawn).SetSkin();
|
|
}
|
|
StinkyControllers[SC.TeamIndex] = SC;
|
|
SC.GotoState('MoveToGuardian', 'Begin');
|
|
}
|
|
|
|
function StinkyControllerCompeledAction(StinkyController SC, int CompletedActionNum)
|
|
{
|
|
local TSCBaseGuardian gnome;
|
|
|
|
gnome = TeamBases[SC.TeamIndex];
|
|
if ( !gnome.bActive ) {
|
|
SC.Pawn.Suicide(); // base does not exist - stinky clot has nothing to do
|
|
}
|
|
if ( SC.ActionNum >= SC.MoveTargets.length ) {
|
|
gnome.MoveToShop(TeamShops[SC.TeamIndex]);
|
|
SC.Pawn.Suicide(); // nothing else to do = die!
|
|
}
|
|
else {
|
|
if ( CompletedActionNum == 0 ) {
|
|
SC.TakeActor(gnome);
|
|
gnome.SetRelativeLocation(gnome.GameObjOffset);
|
|
gnome.SetRelativeRotation(gnome.GameObjRot);
|
|
gnome.Holder = SC.StinkyClot;
|
|
gnome.bHeld = true;
|
|
ZedSpawnLoc = ZSLOC_RANDOM;
|
|
ScrnBalanceMut.GameRules.AdjustZedSpawnRate(); // reset boring votes
|
|
}
|
|
SC.GotoState('MoveToShop', 'Begin');
|
|
}
|
|
}
|
|
|
|
// show path to base instead of shop
|
|
function ShowPathTo(PlayerController P, int DestinationIndex)
|
|
{
|
|
local TSCBaseGuardian gnome;
|
|
|
|
gnome = TeamBases[P.PlayerReplicationInfo.Team.TeamIndex];
|
|
if ( gnome == none || gnome.bHidden || TSCGRI.AtOwnBase(P.Pawn) )
|
|
{
|
|
ScrnPlayerController(P).ServerShowPathTo(255); // turn off
|
|
return;
|
|
}
|
|
|
|
if ( P.FindPathToward(gnome, false) != None ) {
|
|
Spawn(BaseWhisp, P,, P.Pawn.Location);
|
|
}
|
|
}
|
|
|
|
function KillRemainingZeds(bool bForceKill)
|
|
{
|
|
super.KillRemainingZeds(bForceKill);
|
|
if (bForceKill) {
|
|
KillAllStinkyClots();
|
|
}
|
|
}
|
|
|
|
function KillAllStinkyClots()
|
|
{
|
|
local array <StinkyClot> Monsters;
|
|
local StinkyClot M;
|
|
local int i;
|
|
|
|
// kill all stinky clots
|
|
foreach DynamicActors(class 'StinkyClot', M) {
|
|
if(M.Health > 0 && !M.bDeleteMe)
|
|
Monsters[Monsters.length] = M;
|
|
}
|
|
for ( i=0; i<Monsters.length; ++i )
|
|
Monsters[i].Suicide();
|
|
}
|
|
|
|
function DoBossDeath()
|
|
{
|
|
KillAllStinkyClots();
|
|
super.DoBossDeath();
|
|
}
|
|
|
|
|
|
State MatchInProgress
|
|
{
|
|
function DoWaveEnd()
|
|
{
|
|
local byte t;
|
|
|
|
if ( bSingleTeamGame )
|
|
super(ScrnGameType).DoWaveEnd(); // bypass TSC
|
|
else
|
|
super.DoWaveEnd();
|
|
|
|
if ( ScrnGameLength == none || ScrnGameLength.Wave.bOpenTrader ) {
|
|
KillAllStinkyClots();
|
|
}
|
|
else {
|
|
// Wave ended but trader doors are closed.
|
|
// Keep the stinky clots but apply new trader destinations.
|
|
for ( t = 0; t < 2; ++t ) {
|
|
if ( StinkyControllers[t] != none && StinkyControllers[t].MoveTargets.length != 0 ) {
|
|
StinkyControllers[t].MoveTargets[StinkyControllers[t].MoveTargets.length-1] = TeamShops[t].TelList[t];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function BattleTimer()
|
|
{
|
|
super.BattleTimer();
|
|
|
|
if ( NextStinkySpawnTime < Level.TimeSeconds
|
|
&& (bWaveBossInProgress || TotalMaxMonsters > 0)
|
|
&& ( (StinkyControllers[1] == none && TeamBases[1].bActive)
|
|
|| (!bSingleTeamGame && StinkyControllers[0] == none && TeamBases[0].bActive) )
|
|
)
|
|
{
|
|
SpawnStinkyClot();
|
|
NextStinkySpawnTime = Level.TimeSeconds + 5; // if failed to spawn, try again in 5 seconds
|
|
}
|
|
}
|
|
|
|
function StartWaveBoss()
|
|
{
|
|
local byte t;
|
|
|
|
if ( bSingleTeamGame )
|
|
t = 1; // skip red team
|
|
|
|
while ( t < 2 ) {
|
|
if ( !TeamBases[t].bActive ) {
|
|
// if players have lost base during the Trader Time
|
|
TeamBases[t].MoveToShop(TeamShops[t]);
|
|
TeamBases[t].ScoreOrHome();
|
|
}
|
|
++t;
|
|
}
|
|
|
|
NextStinkySpawnTime = Level.TimeSeconds + 30; // give enough time for Boss to spawn
|
|
super.StartWaveBoss();
|
|
}
|
|
|
|
function float GetMinSpawnDelay()
|
|
{
|
|
local float result;
|
|
|
|
result = super.GetMinSpawnDelay();
|
|
if ( TeamBases[0].bHeld || TeamBases[1].bHeld )
|
|
result *= 3.0; // slower spawns when Guardian is carried
|
|
return result;
|
|
}
|
|
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
StinkyClass=Class'ScrnBalanceSrv.StinkyClot'
|
|
OvertimeTeamMoneyPenalty=0.000000
|
|
MinBaseZ=-500.000000
|
|
MaxBaseZ=500.000000
|
|
BaseGuardianClasses(0)=Class'ScrnBalanceSrv.TheGuardianRed'
|
|
BaseGuardianClasses(1)=Class'ScrnBalanceSrv.TheGuardianBlue'
|
|
ZedSpawnLoc=ZSLOC_CLOSER
|
|
bSingleTeamGame=True
|
|
bUseEndGameBoss=True
|
|
GameName="Follow the Guardian"
|
|
Description="Non-competitive TSC version for single team."
|
|
ScreenShotName="TSC_T.Team.SteampunkLogo"
|
|
}
|