Pitchaya Kunarochrakse / Gameplay, Tools Developer 

LinkedIn E-mail

Blastrobots

Genre : 3D Top-Down Shooter

Engine : UDK

Role : Programmer

Responsible for : 

  • Implemented weapons’ mechanics and functionality
    • Weapon system
    • Normal gun
      • Flamethrower
      • Laser
      • Lemon gun – aka Chain gun, Pulse gun, Default gun
      • Shotgun
    • Special shot – aka Combo shot
      • BDL (Big Dang Laser)
      • Chain Lightning
      • Spinning Flame Sword
      • Napalm (Meteor-like fireball)
      • Rockettes (Rocket Barrage)
      • Travolta (A light ball which spread laser beams)
      • YoYo (Boss’s special shot)
  • Implemented projectiles, particle effects and visual feedback
    • Player’s blinking red when get shot
    • Most projectiles coming out from any guns in the game (projectiles’ behavior, eg. speed, trajectory etc.)
  • Implemented player’s UI and abilities
    • Player’s shield
    • Player’s combo shot interact with controller
  • Pickup system
  • Collaborate, sharing the ideas with level designers about game mechanics and balance. Setting up config files so they can change the value easier.
  • Help artists overcome technical difficulties; for example, setting up trigger for animated objects, moving the main characters according to their move speed.

Team size : 14

Download Blastrobots

Code Sample

class BlastroWeapon extends UTWeap_ShockRifle;

//var array attachedMesh;
var SkeletalMeshComponent attachedMesh[2];
var FireMode FireModeSlot[ 3 ];

var float fillRageInterval;
var bool bCanfire;

// detect firing mode 0,1
var bool isLeftWeaponFire;
var bool isRightWeaponFire;
var float lastComboFireAttempt;

var FireMode currentMode;

var byte currentSlot;

var BlastroProjectileGenerator lastShot;

var AudioComponent specialWeaponSound;

//=========================================================================
//=========================================================================
function CancelFire()
{
	if ( lastShot != none )
		lastShot.Destroy();
}

//=========================================================================
//=========================================================================
simulated function CustomFire()
{
	Fire( 0 );
}

//=========================================================================
//=========================================================================
simulated function Projectile ProjectileFire()
{
	Fire( 1 );
	return none;
}

//=========================================================================
//=========================================================================
simulated function PostBeginPlay()
{
	updateWeaponAttachment( 0 );
	super.PostBeginPlay();
}

//=========================================================================
//=========================================================================
function SetFireMode( int slot, FireModeType type )
{
	local BlastroGame game;
	local int otherSlot;
	local FireMode nextMode;

	if ( slot == 0 ) otherSlot = 1;
	else otherSlot = 0;

	game = BlastroGame( WorldInfo.Game );

	nextMode = game.getFireMode( type );
	if ( nextMode == FireModeSlot[ otherSlot ] && type != MODE_LEMON )
	{
		FireModeSlot[ otherSlot ] = FireModeSlot[ slot ];
	}

	FireModeSlot[ slot ] = nextMode;
	FireModeSlot[ 2 ] = game.getComboMode( FireModeSlot[ 0 ], FireModeSlot[ 1 ] );

	if (FireModeSlot[ 2 ] == none)
	{
		stopFillRage();
	}
	else
	{
		startFillRage();
	}

	// reset rage when change the weapon
	BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo).rage = 0.0;

	updateWeaponAttachment( slot );

	StopFire(slot);
}

function updateWeaponAttachment(int slot)
{
	local int i;
	local BlastroPawn bPawn;

	bPawn = BlastroPawn( Instigator );
	if ( bPawn == none ) return;

	// attached mesh to player
	if ( slot != 2 && Instigator != none )
	{
		for (i = 0; i < 2; ++i)
		{
			if (attachedMesh[ i ] != none) attachedMesh[ i ].DetachFromAny();
			attachedMesh[ i ] = new class'SkeletalMeshComponent';
			attachedMesh[ i ].SetSkeletalMesh( FireModeSlot[ i ].attachedToPlayerMesh );

			//bPawn.BlastroMesh.AttachComponentToSocket( attachedMesh[ i ], ( i == 0 ) ? 'weapon_01' :'weapon_02');
			bPawn.Mesh.AttachComponentToSocket( attachedMesh[ i ], ( i == 0 ) ? 'WeaponPoint' :'DualWeaponPoint');
		}
	}
}

function startFillRage()
{
	SetTimer(fillRageInterval, true, 'fillRageBar');
}

function stopFillRage()
{
	SetTimer(0.0, true, 'fillRageBar');
}

function fillRageBar()
{
	local BlastroPlayerReplicationInfo repInfo;

	repInfo = BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo);
	if (repInfo.rageInUse) return;

	repInfo.rage = Min( repInfo.maxRageValue, repInfo.rage + FireModeSlot[ 2 ].mod_combo_fillRage );
}

//=========================================================================
//=========================================================================
simulated function StartFire(byte FireModeNum)
{
	local FireMode mode;

	mode = FireModeSlot[ FireModeNum ];
	currentSlot = FireModeNum;
	currentMode = mode;

	//if ( BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo).rageInUse )
	if ( !bCanfire )
	{
		return;
	}

	if ( mode == none ){
		lastComboFireAttempt = WorldInfo.TimeSeconds;
		return;
	}

	if ( FireModeNum == 0 )
	{
		UTPawn(Instigator).CurrentWeaponAttachment.MuzzleFlashPSCTemplate = mode.muzzle;
	}
	else if (FireModeNum == 1)
	{
		UTPawn(Instigator).CurrentWeaponAttachment.MuzzleFlashAltPSCTemplate = mode.muzzle;
	}

	WeaponFireSnd[ FireModeNum ] = mode.fireSound;
	FireInterval[ FireModeNum ] = 1.0 / ( mode.mod_fastRateOfFire + 1 );

	super.StartFire(FireModeNum);
}

//=========================================================================
//=========================================================================
simulated function Fire( int slot )
{
	local Vector FireLoc;
	local FireMode mode;

	mode = FireModeSlot[ slot ];

	if (slot == 2)
	{
		if (BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo).rage < BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo).maxRageValue)
		{
			// not enough rage
			return;
		}

		WeaponPlaySound( mode.fireSound );
	}

	// tell remote clients that we fired, to trigger effects
	IncrementFlashCount();

	FireLoc = GetPhysicalFireStartLoc();
	mode.Fire( self, FireLoc, Instigator.Rotation/*GetAdjustedAim( FireLoc ) */ );
}

simulated function StopFire(byte FireModeNum)
{
	super.StopFire( FireModeNum );
	if( specialWeaponSound != none )
	{
		specialWeaponSound.FadeOut(0.5, 0.2);
	}
}

simulated function WeaponPlaySound(SoundCue Sound, optional float NoiseLoudness)
{
	if ( currentMode != none )
	{
		if(currentMode.Class == class'FlamethrowerMode'){
			if(specialWeaponSound == none){
				specialWeaponSound = Instigator.CreateAudioComponent(Sound, false);
			}
			if(!specialWeaponSound.IsPlaying()){
				specialWeaponSound.Play();
			}
		}
		else{
			super.WeaponPlaySound(sound, NoiseLoudness);
		}
	}
}

//simulated function Fire( int slot )
//{
//	local FireMode mode;
//	local Vector FireLoc;

//	mode = FireModeSlot[ slot ];

//	if ( BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo).rageInUse )
//	{
//		return;
//	}

//	if ( mode == none ){
//		lastComboFireAttempt = WorldInfo.TimeSeconds;
//		return;
//	}

//	if ( slot == 0 )
//	{
//		UTPawn(Instigator).CurrentWeaponAttachment.MuzzleFlashPSCTemplate = mode.muzzle;
//	}
//	else if (slot == 1)
//	{
//		UTPawn(Instigator).CurrentWeaponAttachment.MuzzleFlashAltPSCTemplate = mode.muzzle;
//	}
//	else if (slot == 2)
//	{
//		if (BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo).rage < BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo).maxRageValue)
//		{
//			// not enough rage
//			return;
//		}
//		BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo).rage = 0;
//		BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo).rageInUse = true;
//	}

//	FireInterval[ slot ] = 1.0 / ( mode.mod_fastRateOfFire + 1 );
//	WeaponFireSnd[ slot ]= mode.fireSound;

//	// tell remote clients that we fired, to trigger effects
//	IncrementFlashCount();

//	FireLoc = GetPhysicalFireStartLoc();
//	mode.Fire( self, FireLoc, GetAdjustedAim( FireLoc ) );
//}

//=========================================================================
//=========================================================================
simulated function bool HasAmmo( byte FireModeNum, optional int Amount )
{
	return true;
}

//=========================================================================
//=========================================================================
simulated function bool HasAnyAmmo()
{
	return true;
}

//=========================================================================
//=========================================================================
simulated function Destroyed()
{
	CancelFire();

	Super.Destroyed();
}

//=========================================================================
//=========================================================================
//simulated function SpawnBeam(vector Start, vector End, bool bFirstPerson)
//{
//	local ParticleSystemComponent E;
//	local actor HitActor;
//	local vector HitNormal, HitLocation;

//	if ( End == Vect(0,0,0) )
//	{
//		if ( !bFirstPerson || (Instigator.Controller == None) )
//		{
//	    	return;
//		}
//		// guess using current viewrotation;
//		End = Start + vector(Instigator.Controller.Rotation) * 500;// class'UTWeap_ShockRifle'.default.WeaponRange;
//		HitActor = Instigator.Trace(HitLocation, HitNormal, End, Start, TRUE, vect(0,0,0),, TRACEFLAG_Bullet);
//		if ( HitActor != None )
//		{
//			End = HitLocation;
//		}
//	}

//	//E = WorldInfo.MyEmitterPool.SpawnEmitter(ParticleSystem'BB_Weapon_Particles.Particles.BDL_particle_02', Start);
//	//E.SetVectorParameter('LinkBeamEnd', End);
//	E = WorldInfo.MyEmitterPool.SpawnEmitter(particlesystem'BB_WP_Lazer.Particles.laser_particle_03', Start);
//	E.SetVectorParameter('ShockBeamEnd', End);
//	E.SetDepthPriorityGroup(SDPG_World);
//}

simulated function ParticleSystemComponent SpawnBeam(ParticleSystem EmitterTemplate, vector Start, vector End, Actor attachedActor, name BeamEndName)
{
	local ParticleSystemComponent E;
	//local Vector zero;

	if (attachedActor != none)
	{
		E = WorldInfo.MyEmitterPool.SpawnEmitterCustomLifetime(EmitterTemplate);
		//E = WorldInfo.MyEmitterPool.SpawnEmitter(EmitterTemplate, zero);
		E.SetAbsolute(false, false, false);
		E.SetLODLevel(WorldInfo.bDropDetail ? 1 : 0);
		//E.OnSystemFinished = MyOnParticleSystemFinished;
		E.bUpdateComponentInTick = true;

		attachedActor.AttachComponent(E);
	}
	else
	{
		// no actor to attach
		E = WorldInfo.MyEmitterPool.SpawnEmitter(EmitterTemplate, Start);
	}

	//E.SetVectorParameter('LinkBeamEnd', End);
	E.SetVectorParameter(BeamEndName, End);
	E.SetDepthPriorityGroup(SDPG_World);

	return E;
}

//=========================================================================
//=========================================================================
simulated static function bool PassThroughDamage(Actor HitActor)
{
	return (!HitActor.bBlockActors && (HitActor.IsA('Trigger') || HitActor.IsA('TriggerVolume')))
		|| HitActor.IsA('InteractiveFoliageActor') || HitActor.IsA('BlastroPawn') || HitActor.IsA('EnemyPawn');
}

//=========================================================================
//=========================================================================
//exec function changeWeaponProp(int slotNum, int projParticle, float damage, float damageRadius, float projSpeed, int spreadShot, int fastFire, int piercing, int invisProj, int homing, float homingRadius, float homingCurve, int recursive, float drag, float tlifespan)
//{
//	mod_projParticle[slotNum] = projParticle;
//	mod_damage[slotNum] = damage;
//	mod_damageRadius[slotNum] = damageRadius;
//	mod_projSpeed[slotNum] = projSpeed;
//	mod_spreadShot[slotNum] = spreadShot;
//	mod_fastRateOfFire[slotNum] = fastFire;
//	mod_piercing[slotNum] = piercing;
//	mod_invisProj[slotNum] = invisProj;
//	mod_homing[slotNum] = homing;
//	mod_homingRadius[slotNum] = homingRadius;
//	mod_homingCurve[slotNum] = homingCurve;
//	mod_recursiveShot[slotNum] = recursive;
//	mod_drag[slotNum] = drag;
//	mod_lifespan[slotNum] = tlifespan;
//}

//=========================================================================
//=========================================================================

simulated function ImpactInfo CalcWeaponFire(vector StartTrace, vector EndTrace, optional out array ImpactList, optional vector Extent)
{
	local vector			HitLocation, HitNormal;
	local Actor				HitActor;
	local TraceHitInfo		HitInfo;
	local ImpactInfo		CurrentImpact;

	// Perform trace to retrieve hit info
	foreach TraceActors(class 'Actor', HitActor, HitLocation, HitNormal, EndTrace, StartTrace, Extent, HitInfo, TRACEFLAG_Bullet)
	{
		CurrentImpact.HitActor		= HitActor;
		CurrentImpact.HitLocation	= HitLocation;
		CurrentImpact.HitNormal		= HitNormal;
		CurrentImpact.RayDir		= Normal(EndTrace-StartTrace);
		CurrentImpact.StartTrace	= StartTrace;
		CurrentImpact.HitInfo		= HitInfo;

		ImpactList[ImpactList.Length] = CurrentImpact;

		if (HitActor.IsA('StaticMeshActor') || HitActor.IsA('InterpActor'))
		{
			break;
		}
	}

	// If we didn't hit anything, then set the HitLocation as being the EndTrace location
	if( ImpactList.Length == 0 )
	{
		HitLocation	= EndTrace;
		return CurrentImpact;
	}
	else
	{
		return ImpactList[0];
	}
}

DefaultProperties
{
	//FireInterval[0] = 1.0f; // look in init()

	fillRageInterval = 0.2;

	WeaponFireTypes(0)=EWFT_Custom
	WeaponFireTypes(1)=EWFT_Projectile

	bCanfire = true;
	isLeftWeaponFire = false
	isRightWeaponFire = false
	lastComboFireAttempt = 0

	//WeaponFireSnd[0]=SoundCue'A_Weapon_ShockRifle.Cue.A_Weapon_SR_FireCue'
	//WeaponFireSnd[1]=SoundCue'A_Weapon_ShockRifle.Cue.A_Weapon_SR_AltFireCue'

	//Begin Object Name=Lemon Class='YoyoMode'
	//End Object

	Begin Object Name=Lemon Class='LemonMode'
	End Object

	FireModeSlot[ 0 ] = Lemon
	FireModeSlot[ 1 ] = Lemon
	FireModeSlot[ 2 ] = none

	AttachmentClass=class'BlastroAttachment_MuzzleFlash'

	ProjectileSpawnOffset= 0

}

class SpreadMode extends FireMode;

var float wiggleRatio;

//=========================================================================
//=========================================================================
simulated function Fire( BlastroWeapon weapon, Vector pos, Rotator aim )
{
	local int i;
	local float angleStep;
	local Rotator curAim;
	local int flip;
	local int shotsPerSide;

	angleStep = spreadAngleDegree / mod_numSpread;

	shotsPerSide = mod_numSpread / 2;

	// Shoot one straight forward
	if ( shotsPerSide * 2 < mod_numSpread )
		spawnProjectile( weapon, pos, Vector( aim ) );

	for ( i = 0; i < shotsPerSide; ++i ) 	{ 		for ( flip = -1; flip
1 class RockettesMode extends FireMode; DefaultProperties { projectileClass = class 'BlastroProjectileRockettes' bDestroyLastProjectileOnCancel = true; }
 class BlastroProjectileGenerator extends UTProj_LinkPlasma; //const Pi = 3.1415926535897932; //const RadToDeg = 57.295779513082321600;    // 180 / Pi //const DegToRad = 0.017453292519943296;    // Pi / 180 //const UnrRotToRad = 0.00009587379924285;// Pi / 32768 //const RadToUnrRot = 10430.3783504704527;// 32768 / Pi //const DegToUnrRot = 182.0444; //const UnrRotToDeg = 0.00549316540360483; var float spreadAngleDegree; var bool isExploded; // weapon mods // -1=no_particle, 0=lemon, 1=shotgun, 2=rocket, 3=lazer, 4=flamethrower, 5=chainLighting var int mod_projParticle; var float mod_damage; var float mod_damageRadius; var float mod_projSpeed; var int mod_spreadShot; var int mod_fastRateOfFire; var int mod_piercing; var int mod_invisProj; var int mod_homing; var float mod_homingRadius; var float mod_homingCurve; var int mod_recursiveShot; var float mod_drag; var float mod_lifespan; // boss pattern variable var float tmptime; var float lifetime; var int bulletId; var int numOfBullets; var UTPawn target; var vector initDirection; var int stateParam; var int HomeConeRadians; var float drainRageRate; var float drainRageInterval; // For playing CombotShot Weapon sounds // var AudioComponent weaponSound; var float cueTimer; // state_arg // 0 = normal case // 1 = recursive ignore damage generate actor //========================================================================= //========================================================================= function InitHell(int tmod_projParticle, float tmod_damage, float tmod_damageRadius, 	float tmod_projSpeed, int tmod_spreadShot, 	float tmod_fastRateOfFire, int tmod_piercing, int tmod_invisProj, 	int tmod_homing, float tmod_homingRadius, float tmod_homingCurve, 	int tmod_recursiveShot, 	float tmod_drag, float tmod_lifespan, 	vector Direction, Actor targetActor, int state_arg) { 	// remember mod variable 	self.mod_projParticle   = tmod_projParticle; 	self.mod_damage         = tmod_damage; 	self.mod_damageRadius   = tmod_damageRadius; 	self.mod_projSpeed      = tmod_projSpeed; 	self.mod_spreadShot     = tmod_spreadShot; 	self.mod_fastRateOfFire	= tmod_fastRateOfFire; 	self.mod_piercing		= tmod_piercing; 	self.mod_invisProj		= tmod_invisProj; 	self.mod_homing			= tmod_homing; 	self.mod_homingRadius   = tmod_homingRadius; 	self.mod_homingCurve    = tmod_homingCurve; 	self.mod_recursiveShot  = tmod_recursiveShot; 	self.mod_drag           = tmod_drag; 	self.mod_lifespan       = tmod_lifespan; 	self.stateParam = state_arg; 	//ProjEffects.DeactivateSystem(); 	ExplosionLightClass=none;//CHANGE LATER, CRAIG 	switch (mod_projParticle) 	{ 	case 0 : 		// lemon 		ProjFlightTemplate=ParticleSystem'BB_WP_Pulsecannon.Particles.pulsecannon_particle_02'; 		ProjExplosionTemplate=ParticleSystem'BB_WP_Pulsecannon.Particles.lemon_impact_particle_02'; 		//ProjExplosionTemplate=none; 		ExplosionSound=SoundCue'BlastroSFX.LemonGun.Lemon_Impact_1_Cue'; 		//ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		ExplosionDecal=none; 		ExplosionLightClass=none; 		break; 	case 1 : 		// shotgun 		ProjFlightTemplate=ParticleSystem'BB_WP_Shotgun.Particles.shotgun_particle_03'; 		ProjExplosionTemplate=ParticleSystem'BB_WP_Shotgun.Particles.shotgun_impact_01'; 		ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		ExplosionLightClass=none; 		break; 	case 2 : 		// rocket 		ProjFlightTemplate=ParticleSystem'BB_WP_Missile.Particles.rocket_particle_01'; 		ProjExplosionTemplate=ParticleSystem'BB_Weapon_Particles.Particles.explosion_particle_03'; 		//PlaySound( SoundCue'BlastroSFX.RocketGun.RocketLauncher_Shot29_Cue' ); 		ExplosionSound=ChooseExplosionSound(); 		ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		ExplosionLightClass=none; 		break; 	case 3 : 		// lazer 		ProjFlightTemplate=ParticleSystem'BB_Weapon_Particles.Particles.laser_particle_01'; 		ProjExplosionTemplate=ParticleSystem'BB_Weapon_Particles.Particles.explosion_particle_02'; 		ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		break; 	case 4 : 		// flamethrower 		ProjFlightTemplate=ParticleSystem'BB_WP_Flamethrower.Particles.flamethrower_particle_01'; 		//ProjExplosionTemplate=ParticleSystem'BB_WP_Flamethrower.Particles.smoke_particle_01'; 		ProjExplosionTemplate=none; 		ExplosionSound=none; 		ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		break; 	case 5 : 		// amanda 		ProjFlightTemplate=ParticleSystem'BB_Weapon_Particles.Particles.amandafire_particle_01'; 		//ProjFlightTemplate=ParticleSystem'BB_Weapon_Particles.Particles.amanda_01'; 		ProjExplosionTemplate=none; 		ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		SetDrawScale(5); 		break; 	case 6 : 		// napalm 		ProjFlightTemplate=ParticleSystem'BB_Weapon_Particles.Particles.fireball_01'; 		//ProjExplosionTemplate=ParticleSystem'BB_Weapon_Particles.Particles.explosion_particle_02'; 		ProjExplosionTemplate=none; 		ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		//ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		ExplosionDecal=none; 		SetDrawScale(7); 		break; 	case 7 : 		// napalm tail 		ProjFlightTemplate=ParticleSystem'BB_Weapon_Particles.Particles.napalmtrail_particles_03'; 		//ProjExplosionTemplate=ParticleSystem'WP_ShockRifle.Particles.P_WP_ShockRifle_Ball_Impact'; 		ProjExplosionTemplate=none; 		ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		SetDrawScale(6); 		break; 	case 8 : 		// rockettes 		ProjFlightTemplate=ParticleSystem'BB_Weapon_Particles.Particles.rocket_particle_01'; 		//ProjExplosionTemplate=ParticleSystem'WP_ShockRifle.Particles.P_WP_ShockRifle_Ball_Impact'; 		ProjExplosionTemplate=none; 		ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		break; 	case 9 : 		// flier rockettes 		ProjFlightTemplate=ParticleSystem'BB_Weapon_Particles.Particles.minime_particle_01'; 		//ProjExplosionTemplate=ParticleSystem'WP_ShockRifle.Particles.P_WP_ShockRifle_Ball_Impact'; 		ProjExplosionTemplate=none; 		ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		break; 	case 10 : 		// artillery rain 		//ProjFlightTemplate=ParticleSystem'BB_Weapon_Particles.Particles.fireball_particle_01'; 		ProjFlightTemplate=ParticleSystem'BB_WP_Missile.Particles.rocket_particle_01'; 		ProjExplosionTemplate=ParticleSystem'BB_Weapon_Particles.Particles.explosion_particle_02'; 		//ProjExplosionTemplate=none; 		ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		//ExplosionDecal=none; 		//SetDrawScale(3); 		break; 	case 11 : 		// travolta orb 		ProjFlightTemplate=ParticleSystem'BB_Weapon_Particles.Particles.disco_particle_01'; 		//ProjExplosionTemplate=ParticleSystem'BB_Weapon_Particles.Particles.explosion_particle_02'; 		ProjExplosionTemplate=none; 		ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		//ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		ExplosionDecal=none; 		SetDrawScale(2); 		break; 	case 12 : 		// rocket of rockettes 		ProjFlightTemplate=ParticleSystem'BB_WP_Missile.Particles.rockette_particle_01'; 		ProjExplosionTemplate=ParticleSystem'BB_Weapon_Particles.Particles.explosion_particle_01'; 		//ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		ExplosionSound=none; 		ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		ExplosionLightClass=class'UTGame.UTRocketExplosionLight'; 		break; 	case 13 : 		// chronosphere 		ProjFlightTemplate=ParticleSystem'BB_Item_Particles.Particles.chronosphere_particle_01'; 		//ProjExplosionTemplate=ParticleSystem'BB_Weapon_Particles.Particles.explosion_particle_02'; 		ProjExplosionTemplate=none; 		ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		//ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		ExplosionDecal=none; 		//SetDrawScale(7); 		break; 	case 14 : 		// lazer for travola 		ProjFlightTemplate=ParticleSystem'BB_WP_Lazer.Particles.laser_particle_04'; 		ProjExplosionTemplate=ParticleSystem'BB_Weapon_Particles.Particles.explosion_particle_02'; 		ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		break; 	case 15 : 		// lightningball 		ProjFlightTemplate=ParticleSystem'BB_Weapon_Particles.Particles.lightning_particle_01'; 		//ProjExplosionTemplate=ParticleSystem'BB_Weapon_Particles.Particles.explosion_particle_02'; 		ProjExplosionTemplate=none; 		ExplosionSound=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_ImpactCue'; 		//ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'; 		ExplosionDecal=none; 		SetDrawScale(4); 		break; 	} 	//super.Init(Direction); 	ProjEffects.DeactivateSystem(); 	SpawnFlightEffects(); 	if (mod_projParticle == -2) 	{ 		// use old template, do nothing 	} 	else if (mod_projParticle == -1) 	{ 		// no effect 		ProjEffects.DeactivateSystem(); 		ProjEffects = none; 	} 	// init mod 	Damage = mod_damage; 	DamageRadius = mod_damageRadius; 	Speed = mod_projSpeed; 	if (mod_invisProj > 0)
	{
		SetHidden( true );
	}

	//drag = mod_drag; //1 - mod_drag;
	LifeSpan = mod_lifespan;

	// normally init
	SetRotation(rotator(Direction));

	Velocity = Speed * Direction;
	Velocity.Z += TossZ;
	Acceleration = AccelRate * Normal(Velocity);
}

simulated event HitWall(vector HitNormal, Actor Wall, PrimitiveComponent WallComp)
{
	local KActorFromStatic NewKActor;
	local StaticMeshComponent HitStaticMesh;

	MomentumTransfer = 1.0;

	if ( Wall.bWorldGeometry )
	{
		HitStaticMesh = StaticMeshComponent(WallComp);
		if ( (HitStaticMesh != None) && HitStaticMesh.CanBecomeDynamic() )
		{
				NewKActor = class'KActorFromStatic'.Static.MakeDynamic(HitStaticMesh);
				if ( NewKActor != None )
				{
					Wall = NewKActor;
				}
		}
	}
	ImpactedActor = Wall;

	if ( Instigator != none )
	{
		if ( Instigator.IsA( 'BlastroPawn' ) && !Wall.bStatic && (DamageRadius == 0) )
		{
			Wall.TakeDamage( Damage, InstigatorController, Location, MomentumTransfer * Normal(Velocity), MyDamageType,, self);
		}
	}

	Explode(Location, HitNormal);
	ImpactedActor = None;
}

//=========================================================================
//=========================================================================
simulated function ProcessTouch (Actor Other, vector HitLocation, vector HitNormal)
{
	if ( isNotSameTeam(Instigator, Other) )
	{
		if ( !Other.IsA('Projectile') || Other.bProjTarget )
		{
			MomentumTransfer = (UTPawn(Other) != None) ? 0.0 : 1.0;
			if (stateParam != 1)
			{
				Other.TakeDamage(Damage, InstigatorController, HitLocation, MomentumTransfer * Normal(Velocity), MyDamageType,, self);
			}
			else
			{
				//stateParam = 0;
			}

			if (mod_piercing > 0)
			{
				mod_piercing--;
			}
			else
			{
				Explode(HitLocation, HitNormal);
			}
		}
	}
}

//=========================================================================
//=========================================================================
event Tick( float DeltaTime )
{
	local UTPawn	Victims;
	local float SearchRadius;
	local TraceHitInfo HitInfo;
	local float bestRating;

	local float curRating;

	local float angleDiff;
	local float dist;
	local Vector disp;

	bestRating = 10000;

	SearchRadius = mod_homingRadius;

	if (mod_homing > 0)
	{
		// find target
		if (target == none)
		{
			foreach VisibleCollidingActors( class'UTPawn', Victims, SearchRadius, Location,,,,, HitInfo )
			{
				if ( !Victims.bWorldGeometry && (Victims != Instigator) && (Victims.IsAliveAndWell()) && ( isNotSameTeam(Instigator, Victims) ) )
				{
					disp = Victims.Location - Location;
					dist = VSize( disp );

					if ( SearchRadius != 0 ) // TODO: Logic for when SearchRadius == 0
						dist = dist / SearchRadius;

					angleDiff = Abs( Atan2( Velocity.Y, Velocity.X ) -
						Atan2( disp.Y, disp.X ) );

					if ( angleDiff > 3.14159 )
						angleDiff = 3.14159 * 2.0 - angleDiff;

					if ( angleDiff < HomeConeRadians / 2.0 )
					{
						curRating = angleDiff * dist;

						if ( curRating < bestRating )
						{
							bestRating = curRating;
							target = Victims;
						}
					}
				}
			}
		}
		else
		{
			// homing - 90000 ori
			SetRotation(RInterpTo(Rotation,Rotator(target.Location - Location),DeltaTime,mod_homingCurve,true));
			Velocity = Normal(Vector(Rotation))* VSize( Velocity );

			if (!target.IsAliveAndWell())
			{
				target = none;
			}
		}
	}

	if (mod_drag != 0)
	{
		Velocity -= Velocity * mod_drag * DeltaTime;
	}

	super.Tick(DeltaTime);
}

//=========================================================================
// make projectile doens't hurt teammates
//=========================================================================
simulated function bool HurtRadius
(
	float				BaseDamage,
	float				CurDamageRadius,
	class	DamageType,
	float				Momentum,
	vector				HurtOrigin,
	optional Actor		IgnoredActor,
	optional Controller InstigatedByController = Instigator != None ? Instigator.Controller : None,
	optional bool       bDoFullDamage
)
{
	local Actor	Victim;
	local bool bCausedDamage;
	local TraceHitInfo HitInfo;
	local StaticMeshComponent HitComponent;
	local KActorFromStatic NewKActor;

	// Prevent HurtRadius() from being reentrant.
	if ( bHurtEntry )
		return false;

	bHurtEntry = true;
	bCausedDamage = false;
	foreach VisibleCollidingActors( class'Actor', Victim, CurDamageRadius, HurtOrigin,,,,, HitInfo )
	{
		if ( Victim.bWorldGeometry )
		{
			// check if it can become dynamic
			// @TODO note that if using StaticMeshCollectionActor (e.g. on Consoles), only one component is returned.  Would need to do additional octree radius check to find more components, if desired
			HitComponent = StaticMeshComponent(HitInfo.HitComponent);
			if ( (HitComponent != None) && HitComponent.CanBecomeDynamic() )
			{
				NewKActor = class'KActorFromStatic'.Static.MakeDynamic(HitComponent);
				if ( NewKActor != None )
				{
					Victim = NewKActor;
				}
			}
		}

		if ( !Victim.bWorldGeometry && (Victim != self) && (Victim != IgnoredActor) &&
			( Victim.bCanBeDamaged || Victim.bProjTarget || Victim.IsA( 'DynamicSMActor' ) )
			&& isNotSameTeam(Instigator, Victim) )
		{
			Victim.TakeRadiusDamage(InstigatedByController, BaseDamage, CurDamageRadius, DamageType, Momentum, HurtOrigin, bDoFullDamage, self);
			bCausedDamage = bCausedDamage || Victim.bProjTarget;
		}
	}
	bHurtEntry = false;
	return bCausedDamage;
}

//=========================================================================
// explode at end of LifeTime
//=========================================================================
simulated function Explode(vector HitLocation, vector HitNormal)
{
	isExploded = true;
	super.Explode(HitLocation, HitNormal);
}

//=========================================================================
//=========================================================================
simulated function Destroyed()
{
	// Final Failsafe check for explosion effect
	if (WorldInfo.NetMode != NM_DedicatedServer && !bSuppressExplosionFX)
	{
		if (!isExploded)
		Explode(self.Location, self.Location);
		SpawnExplosionEffects(Location, vector(Rotation) * -1);
	}

	if (ProjEffects != None)
	{
		DetachComponent(ProjEffects);
		WorldInfo.MyEmitterPool.OnParticleSystemFinished(ProjEffects);
		ProjEffects = None;
	}

	if ( weaponSound != none )
	{
		weaponSound.FadeOut( 0.3, 0.3 );
	}

	super.Destroyed();
}

//=========================================================================
// Helper function
//=========================================================================
simulated function AOE_Damage(float Radius, float deltaTime)
{
	local UTPawn	Victims;
	local DynamicSMActor SM_Victims;
	local TraceHitInfo HitInfo;

	// AOE damage projectiles
	foreach VisibleCollidingActors( class'UTPawn', Victims, Radius, Location,,,,, HitInfo )
	{
		if ( !Victims.bWorldGeometry && (Victims != Instigator) && (Victims.IsAliveAndWell()) && ( isNotSameTeam( Instigator, Victims ) ) )
		{
			Victims.TakeDamage(Damage*DeltaTime, Instigator.Controller, Victims.Location, MomentumTransfer * Normal(Velocity), MyDamageType,, self);
		}
	}

	foreach VisibleCollidingActors( class'DynamicSMActor', SM_Victims, Radius, Location,,,,, HitInfo )
	{
		if ( !SM_Victims.bWorldGeometry && ( isNotSameTeam( Instigator, SM_Victims ) ) )
		{
			SM_Victims.TakeDamage(Damage*DeltaTime, Instigator.Controller, SM_Victims.Location, MomentumTransfer * Normal(Velocity), MyDamageType,, self);
		}
	}
}

//=========================================================================
//=========================================================================
simulated function ParticleSystemComponent SpawnBeam(ParticleSystem EmitterTemplate, vector Start, vector End, Actor attachedActor, name BeamEndName)
{
	local ParticleSystemComponent E;
	//local Vector zero;

	if (attachedActor != none)
	{
		E = WorldInfo.MyEmitterPool.SpawnEmitterCustomLifetime(EmitterTemplate);
		//E = WorldInfo.MyEmitterPool.SpawnEmitter(EmitterTemplate, zero);
		E.SetAbsolute(false, false, false);
		E.SetLODLevel(WorldInfo.bDropDetail ? 1 : 0);
		E.OnSystemFinished = MyOnParticleSystemFinished;
		E.bUpdateComponentInTick = true;

		attachedActor.AttachComponent(E);
	}
	else
	{
		// no actor to attach
		E = WorldInfo.MyEmitterPool.SpawnEmitter(EmitterTemplate, Start);
	}

	//E.SetVectorParameter('LinkBeamEnd', End);
	E.SetVectorParameter(BeamEndName, End);
	E.SetDepthPriorityGroup(SDPG_World);

	return E;
}

//=========================================================================
//=========================================================================
function startDrainRage()
{
	SetTimer(drainRageInterval, true, 'drainRageBar');
}

//=========================================================================
//=========================================================================
function stopDrainRage()
{
	SetTimer(0.0, true, 'drainRageBar');
}

//=========================================================================
//=========================================================================
function drainRageBar()
{
	local BlastroPlayerReplicationInfo repInfo;

	repInfo = BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo);
	repInfo.rage = Max( 0, repInfo.rage - drainRageRate );
}

//=========================================================================
//=========================================================================
simulated function ImpactInfo CalcWeaponFire(vector StartTrace, vector EndTrace, optional out array ImpactList, optional vector Extent)
{
	local vector			HitLocation, HitNormal;
	local Actor				HitActor;
	local TraceHitInfo		HitInfo;
	local ImpactInfo		CurrentImpact;

	// Perform trace to retrieve hit info
	foreach TraceActors(class 'Actor', HitActor, HitLocation, HitNormal, EndTrace, StartTrace, Extent, HitInfo, TRACEFLAG_Bullet)
	{
		CurrentImpact.HitActor		= HitActor;
		CurrentImpact.HitLocation	= HitLocation;
		CurrentImpact.HitNormal		= HitNormal;
		CurrentImpact.RayDir		= Normal(EndTrace-StartTrace);
		CurrentImpact.StartTrace	= StartTrace;
		CurrentImpact.HitInfo		= HitInfo;

		ImpactList[ImpactList.Length] = CurrentImpact;

		if (HitActor.IsA('StaticMeshActor') || HitActor.IsA('InterpActor'))
		{
			break;
		}
	}

	// If we didn't hit anything, then set the HitLocation as being the EndTrace location
	if( ImpactList.Length == 0 )
	{
		HitLocation	= EndTrace;
		return CurrentImpact;
	}
	else
	{
		return ImpactList[0];
	}
}

//=========================================================================
//=========================================================================
simulated static function bool PassThroughDamage(Actor HitActor)
{
	return (!HitActor.bBlockActors && (HitActor.IsA('Trigger') || HitActor.IsA('TriggerVolume')))
		|| HitActor.IsA('InteractiveFoliageActor') || HitActor.IsA('BlastroPawn') || HitActor.IsA('EnemyPawn');
}

//=========================================================================
//=========================================================================
function bool isNotSameTeam(Actor a, Actor b)
{
	if ( a.IsA( 'DynamicSMActor' ) && b.IsA( 'EnemyPawn' ) ) return false;
	if ( a.IsA( 'EnemyPawn' ) && b.IsA( 'DynamicSMActor' ) ) return false;

	return a.bWorldGeometry || b.bWorldGeometry ||  a.IsA( 'BlastroPawn' ) != b.IsA( 'BlastroPawn' );
}

simulated function bool ShouldSpawnExplosionLight(vector HitLocation, vector HitNormal)
{
	return false;
}

//=========================================================================
//=========================================================================
simulated function PlayComboShotSound()
{
	if ( weaponSound != none )
	{
		if ( !weaponSound.IsPlaying() )
		{
			weaponSound.Play();
		}
	}
	else
	{
		`log("Error: Trying to play Comboshot sound in ProjectileGenerator before it has been created");
	}
}

simulated function SoundCue ChooseExplosionSound()
{
	local int soundChoice;

	soundChoice = Rand(4);

	switch ( soundChoice )
	{
	case 0:
		return SoundCue'BlastroSFX.ROCKETTES.Firework01_Cue';
		break;
	case 1:
		return SoundCue'BlastroSFX.ROCKETTES.Firework02_Cue';
		break;
	case 2:
		return SoundCue'BlastroSFX.ROCKETTES.Firework03_Cue';
		break;
	case 3:
		return SoundCue'BlastroSFX.ROCKETTES.Firework07_Cue';
		break;
	}
}

simulated function PlayRocketteSound()
{
	local int soundChoice;

	soundChoice = Rand(4);

	switch( soundChoice )
	{
	case 0:
		PlaySound( SoundCue'BlastroSFX.ROCKETTES.RocketLauncher_Shot09_Cue' );
		break;
	case 1:
		PlaySound( SoundCue'BlastroSFX.ROCKETTES.RocketLauncher_Shot13_Cue' );
		break;
	case 2:
		PlaySound( SoundCue'BlastroSFX.ROCKETTES.RocketLauncher_Shot40_Cue' );
		break;
	case 3:
		PlaySound( SoundCue'BlastroSFX.RocketGun.RocketLauncher_Shot29_Cue' );
		 break;
	}
}

//=========================================================================
//=========================================================================
DefaultProperties
{
	drainRageRate = 0;
	drainRageInterval = 0.2;

	isExploded = false;

	ProjFlightTemplate=ParticleSystem'BB_WP_Pulsecannon.Particles.pulsecannon_particle_01'
	//ProjExplosionTemplate=ParticleSystem'WP_ShockRifle.Particles.P_WP_ShockRifle_Ball_Impact'
	ProjExplosionTemplate=none;

	DecalWidth=128.0
	DecalHeight=128.0

	Speed=0
	AccelRate=0.0
	MaxSpeed=0
	MaxEffectDistance=7000.0
	LifeSpan = 0

	spreadAngleDegree = 45.0f
	target = none

	HomeConeRadians = 2.09;

	// Sets timer for SoundCue
	//
	cueTimer = 0.1;
}

class BlastroProjectileRockettes extends BlastroProjectileGenerator;

var ParticleSystem LaserTemplate;
var float timer;

var float Rockettes_Duration;
var int funnelNumber;
var float angleDegree;
var float distanceFromActor;
var float funnelRadius;
var float funnelRateOfFire;

var float funnelRocketDamage;
var float funnelRocketDamageRadius;
var float funnelRocketSpeed;
var float funnelRocketHomingCurve;
var float funnelRocketDuration;

var array< BlastroProjectileGenerator > funnelDrones;

function InitHell(int tmod_projParticle, float tmod_damage, float tmod_damageRadius,
	float tmod_projSpeed, int tmod_spreadShot,
	float tmod_fastRateOfFire, int tmod_piercing, int tmod_invisProj,
	int tmod_homing, float tmod_homingRadius, float tmod_homingCurve,
	int tmod_recursiveShot,
	float tmod_drag, float tmod_lifespan,
	vector Direction, Actor targetActor, int state_arg)
{
	local int i;
	local BlastroProjectileRockettes	SpawnedProjectile;

	funnelDrones.Length = 0;

	if (state_arg == 10)
	{
		super.InitHell(
						-1,     //mod_projParticle[slotNum],
						0,	//mod_damage[slotNum],
						0,      //mod_damageRadius[slotNum],
						0,	//mod_projSpeed[slotNum],
						0,		//mod_spreadShot[slotNum],
						0,		//mod_fastRateOfFire[slotNum],
						0,	//mod_piercing[slotNum],
						0,		//mod_invisProj[slotNum],
						0,		//mod_homing[slotNum],
						0,		//mod_homingRadius[slotNum],
						0,		//mod_homingCurve[slotNum],
						0,		//mod_recursiveShot[slotNum],
						0,		//mod_drag[slotNum],
						Rockettes_Duration,		//mod_lifespan[slotNum],
						Direction, targetActor, 10 );
		BlastroWeapon(Instigator.Weapon).bCanfire = false;
		if (Instigator.Controller.IsA('BlastroPlayerController'))
		{
			BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo).rageInUse = true;
			drainRageRate = BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo).maxRageValue / self.mod_lifespan * drainRageInterval;
			startDrainRage();
		}

		for (i = 0; i < funnelNumber; i++)
		{
			SpawnedProjectile = Spawn(class 'BlastroProjectileRockettes',,, Instigator.Location);

			funnelDrones.AddItem( SpawnedProjectile );

			SpawnedProjectile.InitHell(
						9,     //mod_projParticle[slotNum],
						0,	//mod_damage[slotNum],
						0,      //mod_damageRadius[slotNum],
						0,	//mod_projSpeed[slotNum],
						0,		//mod_spreadShot[slotNum],
						0,		//mod_fastRateOfFire[slotNum],
						1000,	//mod_piercing[slotNum],
						0,		//mod_invisProj[slotNum],
						0,		//mod_homing[slotNum],
						0,		//mod_homingRadius[slotNum],
						0,		//mod_homingCurve[slotNum],
						0,		//mod_recursiveShot[slotNum],
						0,		//mod_drag[slotNum],
						Rockettes_Duration,		//mod_lifespan[slotNum],
						Direction, targetActor, 11+i );
			SpawnedProjectile.timer += i / 10.0;
			SpawnedProjectile.bCollideWorld = false;
		}

		// Setup to intermittenly play firing sounds
		//
		//SetTimer( 0.5, true, 'PlayRocketteSound' );
	}
	else
	{
		super.InitHell(
						tmod_projParticle,
						tmod_damage,
						tmod_damageRadius,
						tmod_projSpeed,
						tmod_spreadShot,
						tmod_fastRateOfFire,
						tmod_piercing,
						tmod_invisProj,
						tmod_homing,
						tmod_homingRadius,
						tmod_homingCurve,
						tmod_recursiveShot,
						tmod_drag,
						tmod_lifespan,
						Direction, targetActor, state_arg );
	}
}

event Tick( float DeltaTime )
{
	local array< UTPawn > targetArr;
	local UTPawn	Victims;
	local TraceHitInfo HitInfo;

	local Rotator r;
	local Vector axisX;
	local Vector axisY;
	local Vector axisZ;

	local BlastroProjectileGenerator	SpawnedProjectile;

	timer += DeltaTime;

	if (stateParam > 10)
	{
		SetDrawScale( 3.0 );
		// position itself
		r = Instigator.Rotation;
		r.Yaw += DegToUnrRot * ( (stateParam - 11) - ( funnelNumber - 1 ) * 0.5 ) * (angleDegree / funnelNumber);
		GetAxes( r, axisX, axisY, axisZ );
		SetLocation( VInterpTo(self.Location, Instigator.Location + (Normal(axisX) * -distanceFromActor), DeltaTime, 3) );
		SetRotation( RInterpTo(Rotation,Instigator.Rotation,DeltaTime,90000,true) );

		// generator
		if (timer > 1.0/funnelRateOfFire)
		{
			PlayRocketteSound();
			timer -= 1.0/funnelRateOfFire;

			// lock target
			foreach VisibleCollidingActors( class'UTPawn', Victims, funnelRadius, Location,,,,, HitInfo )
			{
				if ( !Victims.bWorldGeometry && (Victims != Instigator) && (Victims.IsAliveAndWell()) && ( isNotSameTeam( Instigator, Victims ) ) )
				{
					targetArr.AddItem(Victims);
				}
			}

			// pick random target
			SpawnedProjectile = Spawn(class 'BlastroProjectileRockettes',,, self.Location);
			SpawnedProjectile.InitHell(
						12,     //mod_projParticle[slotNum],
						funnelRocketDamage,//5,	//mod_damage[slotNum],
						funnelRocketDamageRadius,      //mod_damageRadius[slotNum],
						funnelRocketSpeed,	//mod_projSpeed[slotNum],
						0,		//mod_spreadShot[slotNum],
						0,		//mod_fastRateOfFire[slotNum],
						0,	//mod_piercing[slotNum],
						0,		//mod_invisProj[slotNum],
						1,		//mod_homing[slotNum],
						0,		//mod_homingRadius[slotNum],
						funnelRocketHomingCurve,		//mod_homingCurve[slotNum],
						0,		//mod_recursiveShot[slotNum],
						0,		//mod_drag[slotNum],
						funnelRocketDuration,		//mod_lifespan[slotNum],
						Vector(self.Rotation), self, 0 );
			if (targetArr.Length != 0)
			SpawnedProjectile.target = targetArr[Rand(targetArr.Length)];
		}
	}

	super.Tick(DeltaTime);
}

simulated function Destroyed()
{
	local BlastroProjectileGenerator curFunnelDrone;

	if (stateParam == 10)
	{
		foreach funnelDrones( curFunnelDrone )
		{
			curFunnelDrone.Destroy();
		}

		BlastroWeapon(Instigator.Weapon).bCanfire = true;
		if (Instigator.Controller.IsA('BlastroPlayerController'))
		{
			BlastroPlayerReplicationInfo(Instigator.PlayerReplicationInfo).rageInUse = false;
			stopDrainRage();
		}
	}
	super.Destroyed();
}

DefaultProperties
{
	//bCollideWorld = false;

	timer = 0;
	MyDamageType = class 'BlastroDmgType_Rockettes'

	///// Useful variable for Level Designer
	Rockettes_Duration = 4.0;//10.0;
	LaserTemplate = particlesystem'BB_WP_Lazer.Particles.laser_particle_01'

	// funnel variable
	funnelNumber = 5;						//
	angleDegree = 150;						// 150 = spread 150 degree behind player
	distanceFromActor = 150;				//
	funnelRadius = 800;						//
	funnelRateOfFire = 3.0;					//

	// funnel rocket variable
	funnelRocketDamage = 4000;//5000//100;				//
	funnelRocketDamageRadius = 100;			//
	funnelRocketSpeed = 800;				//
	funnelRocketHomingCurve = 15000;		//66000
	funnelRocketDuration = 1.1;				//
}