DESCRIPTION

Akhet is a multiplayer first-person shooter that takes place in a sci-fi Egyptian world. Players take on the roles of avatars of the gods Ra and Anubis, who are fighting to gather the souls of the damned in order to return their gods to the mortal realm.

In order to revive their gods, players must gather the canopic jars containing the souls of the damned and return them to the altar of their god, where they will be absorbed.

QUICK STATISTICS:

Role: Lead Programmer

Team: Pyramid Scheme Productions

  • Size: 8 Developers
    • 2 Artists
    • 4 Level Designers
    • 2 Programmers

Engine: UDK (UE3)

Platforms: Windows

Development Time: 5 months

 

Lead Programmer

  • Represented programming department at lead meetings.
  • Created and estimated tasks for the programming department.
  • Approved all code before it was added to the builds.

Gameplay programmer

  • Wrote weapon code for the Scarab Gun's primary fire, secondary fire, and trap zones.
  • Wrote custom variant of Stockpile game mode for our multiplayer map.

Localization

  • Wrote custom Unrealscript class to localize our in-game text on-the-fly.
  • Checked all localized strings to ensure they rendered correctly.
 

  • Motivation:

    When we came up with the design for this game, we wanted to create futuristic weapons that had interesting Egyptian designs. One idea that we had was a gun that shot deadly scarabs, inspired by the movie The Mummy.


    Design:

    The Scarab Gun needed to fulfill two roles for our game. First, it needed to be our shotgun weapon. Second, it was to fulfill our need for a zoning weapon, so it also needed to place traps. I wanted both of these functions to have great gameplay feel.

    For the primary fire mode, the initial spread is a normal shotgun blast like what comes out of the UT3's scattergun. However, on each ensuing update after the projectiles are released, each projectile generates a random deviation and moves in that direction. This gives the Scarab Gun's blasts a "skittering" buglike motion, which is exactly what I wanted.

    For the secondary fire mode, we decided to create particle swarms when the projectile hits the ground. These are essentially a large particle emitter on top of a collision volume that actually causes the damage effects. Finally, when a player enters the swarm, they begin to take damage over time, and the swarm particle effect is transferred to them to indicate this.


    Code:

    Weapon Class:

    class AK_Weapon_ScarabGun extends AK_Weapon_Base;
    
    var float shotSpreadConeAngle;
    var float burstTimer;
    var float burstTime;
    var int currentBurstCount;
    var int burstCount;
    
    //--------------------------------------------- Firing Modes -----------------------------------------------//
    simulated function CustomFire()
    {
    	local int offsetY, offsetZ;
       	local vector positionWhereWeaponFired;
       	local Rotator instigatorAimRotation;
       	local vector instigatorAimDirectionX, instigatorAimDirectionY, instigatorAimDirectionZ;
    	local class< Projectile > projectileClass;
    
    	local Projectile firedProjectile;
    	local vector randomizedYVector, randomizedZVector;
    
    	IncrementFlashCount();
    
    	if (Role == ROLE_Authority)
    	{
    		// fire position is where the projectile will be spawned
    		positionWhereWeaponFired = GetPhysicalFireStartLoc();
    
    		// get direction our instigator is aiming
    		instigatorAimRotation = GetAdjustedAim( positionWhereWeaponFired );
    		GetAxes( instigatorAimRotation, instigatorAimDirectionX, instigatorAimDirectionY, instigatorAimDirectionZ );
    
    		// get the projectile we are firing 
    		projectileClass = GetProjectileClass();
    
    		for ( offsetZ = -1; offsetZ <= 1; ++offsetZ )
    		{
    			for ( offsetY = -1; offsetY <= 1; ++offsetY )
    			{
    				firedProjectile = Spawn( projectileClass, /*ownerOfSpawnedActor*/, /*stringNameOfSpawnedActor*/, positionWhereWeaponFired, /*rotationOfSpawnedActor*/, /*actorTemplateForSpawnedActor*/, /*boolShouldSpawnFailIfColliding*/);
    				
    				//Offset the projectile's direction by a set amount
    				if( firedProjectile != None )
    				{
    					//Set Projectile's direction to the player's direction plus a little bit of randomness because BUG BULLETS
    					randomizedYVector = ( 0.2 + 0.8 * FRand() ) * shotSpreadConeAngle * offsetY * instigatorAimDirectionY;
    					randomizedZVector = ( 0.2 + 0.8 * FRand() ) * shotSpreadConeAngle * offsetZ * instigatorAimDirectionZ;
    
    					firedProjectile.Init( instigatorAimDirectionX + randomizedYVector + randomizedZVector );
    				}
    			}
    	    }
    	}
    }
    
    //--------------------------------------------------------------------------------------------------------
    simulated function Projectile ProjectileFire()
    {
    	local vector					positionWhereWeaponFired;
    	local AK_Projectile_ScarabNest  SpawnedProjectile;
    
    	// tell remote clients that we fired, to trigger effects
    	IncrementFlashCount();
    
    	if( Role == ROLE_Authority )
    	{
    		// this is the location where the projectile is spawned.
    		positionWhereWeaponFired = GetPhysicalFireStartLoc();
    
    		// Spawn projectile
    		SpawnedProjectile = Spawn( class'AK_Projectile_ScarabNest',,, positionWhereWeaponFired );
    		if( SpawnedProjectile != None && !SpawnedProjectile.bDeleteMe )
    		{
    			SpawnedProjectile.Init( Vector( GetAdjustedAim( positionWhereWeaponFired ) ) );
    		}
    
    		// Return it up the line
    		return SpawnedProjectile;
    	}
    
    	return None;
    }
    
    
    
    //--------------------------------------------------------------------------------------------------------
    DefaultProperties
    {
    	localizationSection = "AK_Weapon_ScarabGun"
    	
    	CrosshairImage=Texture2D'AK_hud.AK_HUD_Reticles'
    	CrossHairCoordinates=( U=0, V=0, UL=256, VL=256 )
    
    	//Key to press to switch to weapon
    	InventoryGroup=3 
    
    	//---------------------------- Firing Mode ----------------------------//
    	shotSpreadConeAngle = 0.08				//JL: Changed from 0.1
    	WeaponFireTypes(0)=EWFT_Custom
    	WeaponProjectiles(0)=class'AK_Projectile_Scarab'
    	WeaponFireTypes(1)=EWFT_Projectile
    	WeaponProjectiles(1)=class'AK_Projectile_ScarabNest'
    
    	
    	WeaponFireSnd[0] = SoundCue'AK_Weapon_Sounds.AK_Weap_Scarab.AK_Weap_ScarabFire_Cue' //This is the sound of the gun firing
    	WeaponFireSnd[1] = SoundCue'AK_Weapon_Sounds.AK_Weap_Scarab.AK_Weap_ScarabFire_Cue' //This is the sound of the gun firing
    
    	//---------------------------- Ammunition ----------------------------//
    	ShotCost(0)=1
    	ShotCost(1)=3
    
    	AmmoCount=12
    	LockerAmmoCount=12
    	MaxAmmoCount=12
    	burstCount = 1
    	burstTime = 0.3
    	maxAmmoRechargedPerSecond = 2
    	secondsBeforeStartingRecharge = 3
    	bAutoCharge=true
    
    	FireInterval(0)=+1.2                                 
    	FireInterval(1)=+0.7
    
    
    	//---------------------------- Appearance ----------------------------//
    	// Mesh and animations used in player's view
    	Begin Object class=AnimNodeSequence Name=MeshSequenceA
    	End Object
    
    	Begin Object Name=FirstPersonMesh
    		SkeletalMesh=SkeletalMesh'AK_ScarabGun.Meshes.AK_Scarabgun_Mesh_01'
    		AnimSets(0)=AnimSet'AK_ScarabGun.AK_ScarabGun_ANIMSET_01'
    		Animations=MeshSequenceA
    		Translation = (X=70.0,Y=25.0,Z=-30.0)
    		Rotation=(Yaw=-16384)
    		Scale= 2.5
    		FOV=60.0
    	End Object
    
    	TeamMaterials(0) = MaterialInstanceConstant'AK_ScarabGun.Textures.AK_ScarabGun_MAT_Ra_INST'
    	TeamMaterials(1) = MaterialInstanceConstant'AK_ScarabGun.Textures.AK_ScarabGun_MAT_Anubis_INST'
    
    	//X = how far down the barrel ( positive = out of the screen )
    	//Y = where it is spawned left/right ( positive = right )
    	//Z = where it is spawned up/down ( positive = up )
    	FireOffset = ( X = 7, Y = 12, Z = -5.0 )
    	PlayerViewOffset=(X=16.0,Y=-5,Z=-3.0)
    
    	WeaponFireAnim(0)=WeaponFire
    	WeaponFireAnim(1)=WeaponAltFire
    
    	InstantHitDamage(0)=100
    	InstantHitDamage(1)=100
    	InstantHitDamageTypes(0)=class'AK_DamageType_ScarabShot'
    	InstantHitDamageTypes(1)=class'AK_DamageType_Jar'
    
    	//Mesh and animations used in 3rd person view
    	AttachmentClass = class'AK_Attachment_ScarabGun'
    
    	MuzzleFlashSocket=Muzzle
    	MuzzleFlashPSCTemplate=ParticleSystem'AK_Particle.Muzzle_Flashes.AK_GenericMuzzleFlash_PS_01'
    	MuzzleFlashAltPSCTemplate=ParticleSystem'AK_Particle.Muzzle_Flashes.AK_GenericMuzzleFlash_PS_01'
    	// Mesh used in the inventory pickup zone
    	Begin Object Name=PickupMesh
    		SkeletalMesh=SkeletalMesh'AK_ScarabGun.Meshes.AK_Scarabgun_Mesh_01'
    	End Object
    	PivotTranslation = ( Y = 10.0 )
    }

    Primary Projectile:

    class AK_Projectile_Scarab extends UTProjectile;
    
    var vector initialFlightVector;
    var float timeSinceLaunch;
    
    function Init(vector Direction)
    {
    	timeSinceLaunch = 0.0;
    	SetRotation(rotator(Direction));
    	initialFlightVector = Direction;
    	Velocity = Speed * Direction;
    	Acceleration = AccelRate * Normal(Velocity);
    }
    
    //Every time we get new time, move the projectile a little bit, to simulate the scarabs "flying."
    simulated function Tick(float DeltaTime)
    {
    	local vector newFlightVector;
    
    	timeSinceLaunch += DeltaTime;
    	if( timeSinceLaunch < 0.1 )
    	{
    		initialFlightVector = Normal( Velocity );
    		return;
    	}
    
    	newFlightVector.X = initialFlightVector.X;
    	newFlightVector.Y = initialFlightVector.Y * 2 * FRand();
    	newFlightVector.Z = initialFlightVector.Z * 2 * FRand();
    
    	Velocity = Speed * newFlightVector;
    }
    
    DefaultProperties
    {
    	Speed=2500 						//DH: Changed from 1400
    	MaxSpeed=5000					//DH: Changed from 5000
    	AccelRate=1000 					//DH: Changed from 3000
    
    	MyDamageType = class'AKHET.AK_DamageType_ScarabShot'
    	Damage=12						//DH: Changed from 26
    	DamageRadius=0
    	MomentumTransfer=0
    
    	ProjFlightTemplate=ParticleSystem'AK_WeaponParticles.Scarab_Bullet.AK_ScarabBullet_PS_01'
    	ProjExplosionTemplate=ParticleSystem'AK_Particle.ImpactParticle.AK_BulletImpact_PS_01'
    	LifeSpan=3.0
    
    	AmbientSound = SoundCue'AK_Weapon_Sounds.AK_Weap_Scarab.AK_Weap_ScarabPri_Cue' //XS:this is the scarab primary projectile sound. 
    
    	RemoteRole=ROLE_SimulatedProxy
    	bNetTemporary=false
    }

    Damage and Effects:

    simulated function SwarmWithScarabs()
    {
    	SetTimer( secondsBetweenSwarmDamage, true, 'SwarmDamageOverTime' );
    	timeSinceSwarmHitSeconds = 0;
    
    	WorldInfo.MyEmitterPool.SpawnEmitter(
    		ParticleSystem'AK_Particle.PawnSwarm',  //Spawn the swarm emitter
    		self.Location, 							//at our position
    		rot( 0, 0, 0 ),							//with normal rotation
    		self ); 								//and attach it to us in case we move
    }
    
    function SwarmDamageOverTime()
    {
    	local float damageThisTick;
    	local Controller damageInstigator;
    	local vector momentumFromHit;
    
    	damageThisTick = swarmDamagePerSecond * secondsBetweenSwarmDamage;
    	damageInstigator = None; //This ideally should come from the original shooter.
    	momentumFromHit = vect( 0, 0, 0 ); //Bugs are too small to give momentum!
    	TakeDamage( damageThisTick, damageInstigator, self.location, momentumFromHit, class 'UTDmgType_Burning' );
    
    	timeSinceSwarmHitSeconds += secondsBetweenSwarmDamage;
    	if( timeSinceSwarmHitSeconds >= swarmLifetimeSeconds )
    		ClearTimer( 'SwarmDamageOverTime' );
    }

    Source File:

    View AK_Weapon_ScarabGun.uc on GitHub

    Back to top of code tabs

  • 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 Snippets:

    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.
    }
    

    Source File:

    View AK_Game.uc on GitHub

    Back to top of code tabs
  • SSG Localization

    While the Text Localizer class was originally made in this game, it was refined in Super Slash n' Grab into its final form. Please check the code breakdown for this class by clicking HERE.


    Back to top of code tabs