Stockpile Game Mode

Motivation

In our second Team Game Project, teams were required to make some variant of the Capture the Flag gametype. We prototyped a few different modes and ultimately decided that the classic Stockpile game mode would work best for our game.

Design

The stockpile game mode logic is split mainly across three classes: the game class, the flag class and the score zone class. The game class manages the game timer, scoring timer and victory conditions for the game, but knows nothing about how points are scored. The flag class manages the core gameplay, including attaching itself to the flag carriers and determining when the flag is in the correct location to score. The score zone class mostly handles the visual effects that occur when the flag is placed and/or scores, but also functions as an attachable actor for the flag.

Code

Game Mode Class

    
        
`define GetAll(Class, ListVar, IterVar) ForEach AllActors(class'`Class', `IterVar) { `ListVar.AddItem(`IterVar); }

class AK_Game extends UTCTFGame;

var const int NUMBER_OF_FLAGS;
var const int NUMBER_OF_SCORE_ZONES;

var AK_Game_ScoreZone scoreZones[ 2 ];

//"States" of the game
var bool gameHasStarted;
var bool gameIsRestarting;
var float secondsToWaitBeforeRestartingGame;

//Stockpile timers
var int timeBetweenScoresSeconds;
var int secondsBeforeFlagResets;

var int defaultNumberOfScoresToWin;

//--------------------------------------------- Clocks and Timers ---------------------------------------------//
function Timer()
{
    local int i;

    if( !gameHasStarted || gameIsRestarting )
        return;


    if( AK_GameReplicationInfo( GameReplicationInfo ).timeLeftUntilScoringSeconds <= 0 )
    {
        CheckFlagsAndScore();
        if( CheckEndGame( None, "" ) == true )
        {
            EndTheGame();
        }
        else
        {
            ResetFlagsAndBases();
            TriggerGlobalEventClass(class'AK_Kismet_FlagReset',self, 0);        //RW: Code added to trigger Kismet event
        }
    }

    for( i = 0; i < NUMBER_OF_SCORE_ZONES; ++i )
    {
        if( !scoreZones[ i ].hostingFlag )
            continue;
        CheckAndMaybeTickFlagTimer( scoreZones[ i ], AK_GameReplicationInfo( GameReplicationInfo ) );
    }
}

function CheckAndMaybeTickFlagTimer( AK_Game_ScoreZone scoreZone, AK_GameReplicationInfo timerOwner )
{
    local int i;
    local AK_Pawn pawnIterator;
    local array< AK_Pawn > allPawnsInLevel;
    local bool enemyTeamInZone;
    local AK_Pawn enemyPawn;

    enemyTeamInZone = false;
    `GetAll( AK_Pawn, allPawnsInLevel, pawnIterator )
    for( i = 0; i < allPawnsInLevel.Length; ++i )
    {
        //If the pawn is too far away, we don't care
        if( vsize( scoreZone.Location - allPawnsInLevel[ i ].Location ) > scoreZone.radiusOfScoreZone )
            continue;

        //If defenders are in the zone, then reset the timer and don't do anything else
        if( allPawnsInLevel[ i ].GetTeamNum() == scoreZone.DefenderTeamIndex )
        {
            timerOwner.timeLeftUntilFlagIsReturned[ scoreZone.DefenderTeamIndex ] = secondsBeforeFlagResets;
            return;
        }

        enemyTeamInZone = true;
        enemyPawn = allPawnsInLevel[ i ];
    }

    if( enemyTeamInZone )
    {
        `log( "TICK");
        --timerOwner.timeLeftUntilFlagIsReturned[ scoreZone.DefenderTeamIndex ];
    }

    if( timerOwner.timeLeftUntilFlagIsReturned[ scoreZone.DefenderTeamIndex ] <= 0 )
    {
        `log( "SENDING FLAG HOME");
        scoreZone.SendHostedFlagHome( enemyPawn );
        timerOwner.timeLeftUntilFlagIsReturned[ scoreZone.DefenderTeamIndex ] = secondsBeforeFlagResets;
    }
}








//---------------------------------------------- Game State Handling ----------------------------------------------//
//These are in order of their usage!
function InitGameReplicationInfo()
{
    Super.InitGameReplicationInfo();

    AK_GameReplicationInfo( GameReplicationInfo ).timeBetweenFlagScoresSeconds = timeBetweenScoresSeconds;
    AK_GameReplicationInfo( GameReplicationInfo ).timeLeftUntilScoringSeconds = timeBetweenScoresSeconds;
    AK_GameReplicationInfo( GameReplicationInfo ).timeLeftUntilFlagIsReturned[ 0 ] = secondsBeforeFlagResets;
    AK_GameReplicationInfo( GameReplicationInfo ).timeLeftUntilFlagIsReturned[ 1 ] = secondsBeforeFlagResets;
    AK_GameReplicationInfo( GameReplicationInfo ).goalScore = defaultNumberOfScoresToWin;
}

function PostBeginPlay()
{
    local int i;
    local AK_Game_ScoreZone zoneIterator;
    local array< AK_Game_ScoreZone > allScoreZonesInLevel;

    Super.PostBeginPlay();

    `GetAll( AK_Game_ScoreZone, allScoreZonesInLevel, zoneIterator )
    `assert( allScoreZonesInLevel.Length == NUMBER_OF_SCORE_ZONES );

    for( i = 0; i < allScoreZonesInLevel.Length; ++i )
    {
        scoreZones[ allScoreZonesInLevel[ i ].DefenderTeamIndex ] = allScoreZonesInLevel[ i ];
    }
}

function StartMatch()
{
    Super.StartMatch();
    gameHasStarted = true;
}

function bool CheckEndGame( PlayerReplicationInfo Winner, string Reason )
{
    local UTCTFFlag winningTeamsFlag;
    local Controller gameController;

    if( Teams[ 0 ].Score < GoalScore && Teams[ 1 ].Score < GoalScore )
        return false;
    //Check against mutator win conditions. If they say we can't win, we can't win.
    if ( CheckModifiedEndGame(Winner, Reason) )
        return false;

    //-----Past this point, we are assuming the game is over -----//
    if ( Teams[1].Score > Teams[0].Score )
    {
        GameReplicationInfo.Winner = Teams[1];
    }
    else if( Teams[1].Score < Teams[0].Score )
    {
        GameReplicationInfo.Winner = Teams[0];
    }
    else
    {
        GameReplicationInfo.Winner = Teams[0];
    }

    //Find the winning team's flag and focus the camera on it
    winningTeamsFlag = UTCTFTeamAI(UTTeamInfo(GameReplicationInfo.Winner).AI).FriendlyFlag;
    EndGameFocus = winningTeamsFlag.HomeBase;

    //Tell all players the game is over and to focus their camera on the winning team.
    EndTime = WorldInfo.RealTimeSeconds + EndTimeDelay;
    foreach WorldInfo.AllControllers(class'Controller', gameController)
    {
        gameController.GameHasEnded( EndGameFocus, (gameController.PlayerReplicationInfo != None) && (gameController.PlayerReplicationInfo.Team == GameReplicationInfo.Winner) );
    }
    winningTeamsFlag.HomeBase.SetHidden(False);

    return true;
}

function EndTheGame()
{
    gameIsRestarting = true;
    setTimer( secondsToWaitBeforeRestartingGame, false, nameof( PerformEndGameHandling ) );
}

function PerformEndGameHandling()
{
    super.PerformEndGameHandling();
    RestartGame();
}

function Reset()
{
    AK_GameReplicationInfo( GameReplicationInfo ).timeBetweenFlagScoresSeconds = timeBetweenScoresSeconds;
    AK_GameReplicationInfo( GameReplicationInfo ).timeLeftUntilScoringSeconds = timeBetweenScoresSeconds;
}





//---------------------------------------------- Score Checking ----------------------------------------------//
simulated function CheckFlagsAndScore()
{
    local int i;

    for( i = 0; i < NUMBER_OF_FLAGS; ++i )
    {
        if( Flags[ i ].IsInState( 'Home' ) )
            Teams[ i ].Score += 1;
        else if( Flags[ i ].IsInState( 'PlacedInZone' ) )
            Teams[ 1 - i ].Score += 1;
    }
}

simulated function ResetFlagsAndBases()
{
    local int i;

    for( i = 0; i < NUMBER_OF_FLAGS; ++i )
    {
        Flags[ i ].SendHome( None );
    }

    for( i = 0; i < NUMBER_OF_SCORE_ZONES; ++i )
    {
        scoreZones[ i ].hostingFlag = false;
    }
}

DefaultProperties
{
    NUMBER_OF_FLAGS = 2
    NUMBER_OF_SCORE_ZONES = 2

    Acronym = "AK"
    MapPrefixes(0)="AK"

    MaxPlayersAllowed = 8

    gameHasStarted = false
    gameIsRestarting = false
    secondsToWaitBeforeRestartingGame = 15;

    timeBetweenScoresSeconds = 60           //DH: Changed from 40
    secondsBeforeFlagResets = 5
    defaultNumberOfScoresToWin = 10

    DefaultPawnClass=class'AKHET.AK_Pawn'
    DefaultInventory(0)=class'AKHET.AK_Weapon_WristGun'
    PlayerControllerClass=class'AKHET.AK_PlayerController'
    Hudtype=class'AKHET.AK_Hud_UI'
    GameReplicationInfoClass=class'AKHET.AK_GameReplicationInfo'
    PlayerReplicationInfoClass=class'AKHET.AK_PlayerReplicationInfo'
    bUseClassicHud = true
}

    

Flag Class

    
        
class AK_Stockpile_Flag extends UTCTFFlag
    abstract;

var AK_Stockpile_ScoreZone holdingZone;
var bool inScoreZone;
var int flagIndex;

var class< LocalMessage > flagMessageClass;

var SkeletalMeshComponent OverlayMesh;
var MaterialInstanceConstant OverlayMaterial[2];

replication
{
    if ( bNetDirty )
        flagIndex;
}

simulated function PostBeginPlay()
{
    super.PostBeginPlay();

    if( Role == NM_DedicatedServer )
        return;

    overlayMesh.SetMaterial( 0, OverlayMaterial[0] );
}


//----------------------------------------- States -----------------------------------------//
auto state Home
{
    ignores SendHome, Score, Drop;

    function BeginState( Name PreviousStateName )
    {
        Super.BeginState( PreviousStateName );

        AK_Stockpile_GRI( WorldInfo.GRI ).SetFlagAtSpawn( flagIndex );
        WorldInfo.GRI.bForceNetUpdate = TRUE;
    }

    function EndState( Name NextStateName )
    {
        Super.EndState( NextStateName );
    }

    function SameTeamTouch( Controller C )
    {
        //Touching the flag with the enemy flag does nothing!
    }
}

state Held
{
    ignores SetHolder;

    function SendHome(Controller Returner)
    {
        super.SendHome( Returner );
    }

    function KismetSendHome()
    {
        super.KismetSendHome();
    }

    function Timer()
    {
        super.Timer();
    }

    function BeginState( Name PreviousStateName )
    {
        super.BeginState( PreviousStateName );

        if( holder.GetTeamNum() == 0 )
            AK_Stockpile_GRI( WorldInfo.GRI ).SetFlagHeldByRa( flagIndex );
        else
            AK_Stockpile_GRI( WorldInfo.GRI ).SetFlagHeldByAnubis( flagIndex );

        WorldInfo.GRI.bForceNetUpdate = TRUE;
    }

    function EndState( Name NextStateName )
    {
        super.EndState( NextStateName );
    }
}

//---------------------------------------------------------------------------------
state Dropped
{
    ignores Drop;

    function BeginState( Name PreviousStateName )
    {
        Super.BeginState( PreviousStateName );

        AK_Stockpile_GRI( WorldInfo.GRI ).SetFlagDropped( flagIndex );
        WorldInfo.GRI.bForceNetUpdate = TRUE;
    }

    function EndState( Name NextStateName )
    {
        Super.EndState( NextStateName );
    }

    function SameTeamTouch( Controller C )
    {
        super.SameTeamTouch( C );
    }

    function Timer() // TODO: Look into resetting scalars on endstate too, just in case picked up mid-fade
    {
        super.Timer();
    }
}

//---------------------------------------------------------------------------------
state PlacedInZone
{
    ignores Touch, Drop;

    function BeginState( Name PreviousStateName )
    {
        if( holdingZone.DefenderTeamIndex == 0 )
            AK_Stockpile_GRI( WorldInfo.GRI ).SetFlagPlacedInRaZone( flagIndex );
        else
            AK_Stockpile_GRI( WorldInfo.GRI ).SetFlagPlacedInAnubisZone( flagIndex );

        SetFlagPropertiesToStationaryFlagState();

        SetRotation( holdingZone.Rotation );
        SetBase( holdingZone );
        SetPhysics(PHYS_None);
        inScoreZone = true;
        holdingZone.bForceNetUpdate = true;
        bForceNetUpdate = true;
    }

    function EndState( Name NextStateName )
    {
        inScoreZone = false;
    }

    function SameTeamTouch( Controller C )
    {
        //Touching the flag in this state does nothing!
    }
}




//----------------------------------------- State Changing Functions -----------------------------------------//
function PlaceFlag( AK_Stockpile_ScoreZone scoreZone, Vector locationToPlace )
{
    local PlayerController playerControl;

    playerControl = PlayerController( holder.Controller );
    if( playerControl != None )
    {
        playerControl.ReceiveLocalizedMessage( flagMessageClass, 2 ); //2 is flag placed message
    }

    ClearHolder();
    holdingZone = scoreZone;

    SetLocation( locationToPlace );

    GotoState( 'PlacedInZone' );
}

function SendHome( Controller Returner )
{
    --holdingZone.numberOfHostedFlags;
    holdingZone = None;

    super.SendHome( Returner );
}

function Drop(optional Controller Killer)
{
    //Note to self: holder variable must not be being set
    if( PlayerController( holder.Controller ) != None )
    {
        PlayerController( holder.Controller ).ReceiveLocalizedMessage( flagMessageClass, 1 ); //1 is flag dropped message
    }

    Super.Drop(Killer);
}

//This function is a direct copy of UTCTFFlag's function of the same name; we just don't need the translation.
function SetFlagPropertiesToStationaryFlagState()
{
    //SkelMesh.SetTranslation( vect(0.0,0.0,-40.0) );
    LightEnvironment.bDynamic = TRUE;
    SkelMesh.SetShadowParent( None );
    SetTimer( 5.0f, FALSE, 'SetFlagDynamicLightToNotBeDynamic' );
}


//Most of this function is copied from UTCTFFlag; we have just removed the translation set because it's bad.
function SetHolder( Controller newHolder )
{
    local UTCTFSquadAI S;
    local UTPawn UTP;
    local UTBot B;

    holdingZone = None;

    if( PlayerController( newHolder ) != None )
    {
        PlayerController( newHolder ).ReceiveLocalizedMessage( flagMessageClass, 0 ); //0 is flag taken message
    }

    // when the flag is picked up we need to set the flag translation so it doesn't stick in the ground
    //SkelMesh.SetTranslation( vect(0.0,0.0,0.0) ); //No we don't.
    UTP = UTPawn( newHolder.Pawn );
    LightEnvironment.bDynamic = TRUE;
    SkelMesh.SetShadowParent( UTP.Mesh );

    ClearTimer( 'SetFlagDynamicLightToNotBeDynamic' );

    // AI Related
    B = UTBot( newHolder );
    if ( B != None )
    {
        S = UTCTFSquadAI(B.Squad);
    }
    else if ( PlayerController( newHolder ) != None )
    {
        S = UTCTFSquadAI(UTTeamInfo(newHolder.PlayerReplicationInfo.Team).AI.FindHumanSquad());
    }

    if ( S != None )
    {
        S.EnemyFlagTakenBy(newHolder);
    }

    Super( UTCarriedObject ).SetHolder( newHolder );
    if ( B != None )
    {
        B.SetMaxDesiredSpeed();
    }
}


function bool ValidHolder( Actor Other )
{
    if ( !Super( UTCarriedObject ).ValidHolder(Other) )
    {
        return false;
    }

    if( UTPlayerReplicationInfo( Pawn( Other ).Controller.PlayerReplicationInfo ).bHasFlag )
    {
        return false;
    }

    return true;
}




DefaultProperties
{
    bAlwaysRelevant = true

    flagMessageClass = class'AK_Message_Flag'

    inScoreZone = false

    //Point light on the flag
    Begin Object name=FlagLightComponent
        Brightness=1.25
        LightColor=(R=255,G=255,B=255)
        Radius=192
        CastShadows=false
        bEnabled=true
        LightingChannels=(Dynamic=FALSE,CompositeDynamic=FALSE)
    End Object
    FlagLight=FlagLightComponent
    Components.Add(FlagLightComponent)

    //Overlay mesh used when flag is not visible
    Begin Object Class=SkeletalMeshComponent Name=OverlayMeshComponent
        bAcceptsDynamicDecals=FALSE
        CastShadow=false
        bUpdateSkelWhenNotRendered=false
        bOverrideAttachmentOwnerVisibility=true
        TickGroup=TG_PostAsyncWork
        bPerBoneMotionBlur=true
        DepthPriorityGroup = SDPG_Foreground
        Scale=1.01
    End Object
    Components.Add( OverlayMeshComponent )
    OverlayMesh = OverlayMeshComponent
}

    

Score Zone Class

    
        
class AK_Stockpile_ScoreZone extends UTGameObjective
    abstract;

var array< AK_Stockpile_Flag > hostedFlags;
var repnotify int numberOfHostedFlags;

var const float radiusOfScoreZone;
var const float flagOffsetRadius;

var StaticMeshComponent pedestalMesh;
var MaterialInstanceConstant pedestalMaterial;
var MaterialInstanceConstant pedestalColor;

var StaticMeshComponent outerRingMesh;
var StaticMeshComponent siphonTunnelMesh;

var class< LocalMessage > stockpileMessageClass;

var SoundCue placeFlagSound;
var SoundCue takeFlagSound;

replication
{
    if ( Role == ROLE_Authority )
        numberOfHostedFlags;
}

simulated event ReplicatedEvent ( name eventName )
{
    if( eventName == 'numberOfHostedFlags' )
    {
        SpawnOrDespawnSiphonCircle();
    }
    else
    {
        Super.ReplicatedEvent( eventName );
    }
}

simulated function PostBeginPlay()
{
    super.PostBeginPlay();

    if ( Role < ROLE_Authority )
        return;

    siphonTunnelMesh.SetHidden( true );
    AK_Stockpile_Game( WorldInfo.Game ).RegisterScoreZone( self, DefenderTeamIndex );

    pedestalColor = new(Outer) class'MaterialInstanceConstant';
    pedestalColor.SetParent( pedestalMaterial );
    pedestalMesh.SetMaterial( 0, pedestalColor );
}


reliable server function ServerHandleSiphonCircle()
{
    SpawnOrDespawnSiphonCircle();
}

simulated function SpawnOrDespawnSiphonCircle()
{
    //local vector topOfCylinder, bottomOfCylinder;
    //topOfCylinder = self.location;
    //topOfCylinder.z += 60.0;
    //bottomOfCylinder = self.location;
    //bottomOfCylinder.z -= 60.0;

    if( numberOfHostedFlags > 0 )
    {
        //DrawDebugCylinder( bottomOfCylinder, topOfCylinder, radiusOfScoreZone, 40, 255, 255, 255, true );
        siphonTunnelMesh.SetHidden( false );
    }
    else
    {
        //FlushPersistentDebugLines();
        siphonTunnelMesh.SetHidden( true );
    }
}

simulated function Touch( Actor Other, PrimitiveComponent OtherComp, Vector HitLocation, Vector HitNormal )
{
    local int lastFlagIndex;
    local UTPawn stockpilePawn;
    local bool pawnHasFlag;
    local AK_Stockpile_Flag heldFlag;
    local float fractionOfTotalFlags;
    local vector flagOffsetFromZoneCenter;

    stockpilePawn = UTPawn( Other );
    if ( stockpilePawn != None )
    {
        pawnHasFlag = stockpilePawn.GetUTPlayerReplicationInfo().bHasFlag;

        if( stockpilePawn.GetTeamNum() == DefenderTeamIndex )
        {
            if( pawnHasFlag )
            {
                heldFlag = AK_Stockpile_Flag( stockpilePawn.GetUTPlayerReplicationInfo().GetFlag() );
                if ( heldFlag == None )
                    return;
                heldFlag.ClearHolder();
                hostedFlags.AddItem( heldFlag );

                fractionOfTotalFlags = float( numberOfHostedFlags ) / AK_Stockpile_GRI( WorldInfo.GRI ).totalNumberOfFlags;
                flagOffsetFromZoneCenter.X = flagOffsetRadius * cos( fractionOfTotalFlags * 2 * PI );
                flagOffsetFromZoneCenter.Y = flagOffsetRadius * sin( fractionOfTotalFlags * 2 * PI );
                heldFlag.PlaceFlag( self, self.Location + flagOffsetFromZoneCenter );

                PlaySound( placeFlagSound );

                numberOfHostedFlags += 1;
                if( role == ROLE_Authority )
                {
                    ServerHandleSiphonCircle();
                }
            }
        }
        else
        {
            if( ( numberOfHostedFlags > 0 ) && !pawnHasFlag )
            {
                lastFlagIndex = hostedFlags.Length - 1;
                hostedFlags[ lastFlagIndex ].SetHolder( stockpilePawn.Controller );

                PlaySound( takeFlagSound );

                hostedFlags.Remove( lastFlagIndex, 1 );
                numberOfHostedFlags -= 1;
                if( role == ROLE_Authority )
                {
                    ServerHandleSiphonCircle();

                    //Flag stolen messages start at index 8, and you want the opposite team's from yours
                    BroadcastLocalizedMessage( stockpileMessageClass, 8 + ( 1 - stockpilePawn.GetTeamNum() ), stockpilePawn.PlayerReplicationInfo );
                }
            }
        }
    }
}




DefaultProperties
{
    bAlwaysRelevant=true
    NetUpdateFrequency=1
    RemoteRole=ROLE_SimulatedProxy
    stockpileMessageClass = class'AKHET.AK_Message_Stockpile'

    radiusOfScoreZone = 300.0;
    flagOffsetRadius = 20.0;

    bCollideActors=true
    Begin Object Name=CollisionCylinder
        CollisionRadius=+30.0
        CollisionHeight=+60.0

        CollideActors=true        
        BlockActors=false
        BlockNonZeroExtent=true
        BlockZeroExtent=true
    End Object

    Begin Object Class=DynamicLightEnvironmentComponent Name=FlagBaseLightEnvironment
        bDynamic=FALSE
        bCastShadows=FALSE
    End Object
    Components.Add( FlagBaseLightEnvironment )



    Begin Object Class=StaticMeshComponent Name=PedestalMeshComponent
        StaticMesh=StaticMesh'AK_Flags.Meshes.AK_FlagStand_Mesh_01'
        CastShadow=FALSE
        bCastDynamicShadow=FALSE
        bAcceptsLights=TRUE
        bForceDirectLightMap=TRUE
        LightingChannels=( BSP=TRUE, Dynamic=TRUE, Static=TRUE, CompositeDynamic=TRUE )

        CollideActors=false
        MaxDrawDistance=7000
        Translation=( X=0.0, Y=0.0, Z=-42.0 )
        Rotation=(Roll=32768)
        Scale = 1.0
    End Object
    Components.Add( PedestalMeshComponent )
    pedestalMesh = PedestalMeshComponent
    pedestalMaterial = MaterialInstanceConstant'AK_Flags.Textures.AK_FlagStand_MAT_Nuetral_01_CONST'

    Begin Object Class=StaticMeshComponent Name=OuterRingMeshComponent
        StaticMesh=StaticMesh'AK_Decoration_Pieces.capture_ring.AK_CaptureRing_Mesh_01'
        CastShadow=FALSE
        bCastDynamicShadow=FALSE
        bAcceptsLights=FALSE
        bForceDirectLightMap=TRUE
        LightingChannels=( BSP=TRUE, Dynamic=TRUE, Static=TRUE, CompositeDynamic=TRUE )

        CollideActors=false
        MaxDrawDistance=7000
        Translation=( X=0.0, Y=0.0, Z=-44.0 )           //DH:Z changed from -50
        Scale3D=(X=1.015,Y=1.015,Z=1.0)             //DH: Changed from Scale=1.2
    End Object
    Components.Add( OuterRingMeshComponent )
    outerRingMesh = OuterRingMeshComponent

    
    Begin Object Class=StaticMeshComponent Name=SiphonTunnelMeshComponent
        StaticMesh=StaticMesh'AK_Decoration_Pieces.Siphon_Circle.AK_SiphonCircle_Mesh_Purple_01'
        CastShadow=FALSE
        bCastDynamicShadow=FALSE
        bAcceptsLights=TRUE
        bForceDirectLightMap=TRUE
        LightingChannels=( BSP=TRUE, Dynamic=TRUE, Static=TRUE, CompositeDynamic=TRUE )

        CollideActors=false
        MaxDrawDistance=7000
        Translation=( X=0.0, Y=0.0, Z=-62.0 )
        Scale = 1.0                             //DH: Changed from 1.2
    End Object
    Components.Add( SiphonTunnelMeshComponent )
    siphonTunnelMesh = SiphonTunnelMeshComponent

    placeFlagSound = SoundCue'AK_AmbNoise.ak_music_noise.AK_SiphonParticle_Cue'
    takeFlagSound = SoundCue'AK_AmbNoise.AK_Jar_Pickup_Cue' //sames as picking up from original jar loc.
}