Pitchaya Kunarochrakse / Gameplay, Tools Developer 

LinkedIn E-mail

Ruined


New video for Ruined is coming!

Genre : 1st Person Shooter, Capture the flag

Engine : UDK

Role : Programmer

Responsible for : 

  • Implemented weapons’ mechanics and functionality
    • Normal gun
      • Bolter (for Soldier and Medic class)
      • LazCannon (for Heavy class)
      • Shotgun (for Scout class)
    • Special abilities
      • Shield
      • Healing wave
      • Blackhole
      • Force push
  • Implemented class selection and inventory system
  • Modified UDK AI’s behavior to interaction with game’s environment

Team size : 7

Code Sample

class ruined_script_pawn_base extends UTPawn;

var int RegenPerSec;
var int Energy;
var bool inControlVolume;
var ruined_script_controlvolume cv;
var float speedMod;
var bool isInvis;

// abilities variable
var class forcePushParticle;
var class healingWaveParticle;
var class shieldParticle;
var repnotify SkeletalMeshComponent shieldMesh;

var bool isHealingActivated;
var float healingWaveActivateCost;
var float healingWaveDrainPerSec; // drain user's energy
var float healingWave_lifeDrainDmg;
var float healingWave_energyDrainDmg;
var float healingWave_healDmg;
var float healingWave_radius;

var bool isShieldActivated;
var float shieldActivateCost;
var float shieldDrainPerSec; // drain user's energy
var float shieldModifier;
var float shieldRadius;
var bool isUnderShield;

///// members for the custom mesh
var SkeletalMesh defaultMesh;
var MaterialInterface defaultMaterial0;
var AnimTree defaultAnimTree;
var array defaultAnimSet;
var AnimNodeSequence defaultAnimSeq;
var PhysicsAsset defaultPhysicsAsset;

replication
	{
		if (bNetDirty)
			inControlVolume, isShieldActivated, isUnderShield, isHealingActivated, Energy, shieldMesh, RegenPerSec;
	}

simulated function PostBeginPlay()
{
	MovementSpeedModifier *= speedMod;

	super.PostBeginPlay();

	`log("The shield mesh is:"@shieldMesh);

	// This timer is set for the player cheating health regeneration
	SetTimer( 0.5f, true, 'EnergyRegen');
}

function AddDefaultInventory()
{
	// BLANK FUNCTION - DO NOT DELETE

	// This function overrides the base function so NOTHING is added to the player's inventory by default
}

function EnergyRegen()
{
	//if ( Controller.IsA( 'PlayerController' ) && !IsInPain() )
	//{
	//	Health += RegenPerSec;
	//}
	local Actor	pBase;
	local TraceHitInfo HitInfo;

	Energy = Min(Energy+RegenPerSec, 100);
	if ( Controller.IsA( 'ruined_script_controller_ai' ) )
	{
		Energy = Energy+1000;
	}

	isUnderShield = false;
	foreach VisibleCollidingActors( class'Actor', pBase, shieldRadius, Location,,,,, HitInfo )
	{
		if ( !pBase.bWorldGeometry && (pBase != self) && pBase.GetTeamNum() == GetTeamNum() )
		{
			if (ruined_script_pawn_base(pBase).isShieldActivated)
			{
				isUnderShield = true;
				break;
			}
		}
	}

	//isInvis = !isInvis;
	//SetHidden(isInvis);
	SetHidden(isUnderShield);

	//`log("Energy: ", true,name);
	//`log(Energy, true,name);
}

///// Abilities
function forcePush()
{
	local Actor	Victims;
	local float DamageRadius;
	local Vector HurtOrigin;
	local TraceHitInfo HitInfo;

	local Vector dir;
	local float portion;

	local Projectile P;

	DamageRadius = 275;
	HurtOrigin = Location;

	foreach VisibleCollidingActors( class'Actor', Victims, DamageRadius, HurtOrigin,,,,, HitInfo )
	{
		//if ( !Victims.bWorldGeometry && (Victims != self) && (Victims != IgnoredActor) && (Victims.bCanBeDamaged || Victims.bProjTarget) )
		if ( !Victims.bWorldGeometry && (Victims != self) && (Victims != Instigator) )
		{
			//`log("actor", true, name);
			//`log(Victims.ObjectArchetype.Name, true, name);

			// force push
			Victims.TakeRadiusDamage(Instigator.Controller, 0, 1500.0f, class 'UTDmgType_LinkPlasma', 200000.0f, HurtOrigin, true, self);
		}
	}

	ForEach AllActors(class'Projectile', P)
	{
		if (P.ObjectArchetype.Class != class'ruined_script_projectile_powerthrust')
		{
			//`log("actor", true, name);
			//`log(P.ObjectArchetype.Name, true, name);

			dir = P.Location - HurtOrigin;
			portion = VSize(dir) / 1500.0f;
			if (portion < 1.0f)
			{
				P.Velocity = P.Velocity + dir * portion * (10.0f*VSize(P.Velocity));

			}
		}
	}

	Spawn(forcePushParticle,,,Location);
}

function healingWaveTimer()
{
	isHealingActivated = !isHealingActivated;

	if (isHealingActivated)
	{
		if (Energy < healingWaveActivateCost)
		{
			isHealingActivated = false;
		}
		else
		{
			Energy -= healingWaveActivateCost;
			SetTimer(0.5f, true, 'healingWave');
		}
	}
	else
	{
		SetTimer(0.0f, true, 'healingWave');
	}
}

function healingWave()
{
	//Spawn(healingWaveParticle,,,Location);
	//Health = Health + RegenPerSec;
	//return;

	local Actor	Victims;
	local float DamageRadius;
	local Vector HurtOrigin;
	local TraceHitInfo HitInfo;

	local Vector zeroVec;

	if (Energy < healingWaveDrainPerSec)
	{
		isHealingActivated = false;
		SetTimer(0.0f, true, 'healingWave');
		return;
	}
	else
	{
		Energy -= healingWaveDrainPerSec;
	}

	DamageRadius = healingWave_radius;
	HurtOrigin = Location;

	foreach VisibleCollidingActors( class'Actor', Victims, DamageRadius, HurtOrigin,,,,, HitInfo )
	{
		//if ( !Victims.bWorldGeometry && (Victims != self) && (Victims != IgnoredActor) && (Victims.bCanBeDamaged || Victims.bProjTarget) )
		if ( !Victims.bWorldGeometry && (Victims != self) && (Victims != Instigator) )
		{
			//`log("actor", true, name);
			//`log(Victims.ObjectArchetype.Name, true, name);

			// force push
			if (Victims.GetTeamNum() == GetTeamNum())
			{
				// on same team, heal
				Victims.HealDamage(healingWave_healDmg, Controller, class 'UTDmgType_LinkPlasma');
			}
			else
			{
				// other team, drain
				Victims.TakeDamage(healingWave_lifeDrainDmg, Controller, Victims.Location, zeroVec, class 'UTDmgType_LinkPlasma', HitInfo, self);
				//ruined_script_pawn_base(Victims).Energy -= healingWave_energyDrainDmg;
				ruined_script_pawn_base(Victims).UseEnergy( healingWave_energyDrainDmg );

				//`log(Victims.GetHumanReadableName, true,name);
				//`log("DRAIN : Health/MP", true,name);
				//`log(ruined_script_pawn_base(Victims).Health, true,name);
				//`log(ruined_script_pawn_base(Victims).Energy, true,name);
			}

			//Victims.TakeRadiusDamage(Instigator.Controller, 0, 1500.0f, class 'UTDmgType_LinkPlasma', 200000.0f, HurtOrigin, true, self);
		}
	}

	self.HealDamage(healingWave_healDmg, Controller, class 'UTDmgType_LinkPlasma');
	Spawn(healingWaveParticle,,,Location);
}

event UseEnergy(float amount)
{
	Energy -= amount;
	if (Energy < 0)
	{
		Energy = 0;
	}
}

simulated function ShieldTimer()
{
	isShieldActivated = !isShieldActivated;

	if (isShieldActivated)
	{
		if (Energy < shieldActivateCost)
		{
			isShieldActivated = false;
			//DetachComponent(shieldMesh);
		}
		else
		{
			`log("Activating shield:"@shieldMesh);
			Energy -= shieldActivateCost;
			SetTimer(0.5f, true, 'shield');
			//Spawn(shieldParticle,,,Location);

			//AttachComponent(shieldMesh);
		}
	}
	else
	{
		SetTimer(0.0f, true, 'shield');
		//DetachComponent(shieldMesh);
	}

}

simulated function shield()
{
	if (Energy < shieldDrainPerSec) 	{ 		isShieldActivated = false; 		SetTimer(0.0f, true, 'shield'); 		//DetachComponent(shieldMesh); 		return; 	} 	else 	{ 		Energy -= shieldDrainPerSec; 	} 	Spawn(shieldParticle,,,Location); } event TakeDamage(int Damage, Controller EventInstigator, vector HitLocation, vector Momentum, class DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser) { 	local float tmpDmg; 	tmpDmg = Damage; 	if (isShieldActivated) 	{ 		tmpDmg *= shieldModifier; 		if (EventInstigator != self.Controller) 		EventInstigator.Pawn.TakeDamage(tmpDmg, EventInstigator, HitLocation, Momentum, DamageType, HitInfo, EventInstigator.Pawn); 	} 	else 	{ 		if (isUnderShield) 		{ 			tmpDmg *= shieldModifier; 		} 		Super.TakeDamage(tmpDmg, EventInstigator, HitLocation, Momentum, DamageType, HitInfo, DamageCauser); 	} } ///// end of Abilities function bool Died(Controller Killer, class damageType, vector HitLocation) { 	if ( inControlVolume ) 	{ 		cv.RemovePlayer(self); 		cv.RemovePlayer(self); 		inControlVolume = false; 	} 	return super.Died(Killer, damageType, HitLocation); } function bool hasEnergy(int requiredEnergy) { 	return (Energy >= requiredEnergy);
}

function Inventory getHeatBeam()
{
	local ruined_script_weapon_heat_beam weap;
	foreach InvManager.InventoryActors(class'ruined_script_weapon_heat_beam', weap)
	{
		return weap;
	}

	return None;
}

//simulated function SetCharacterClassFromInfo(class Info)
//{
//	super.SetCharacterClassFromInfo(Info);

//	Mesh.SetSkeletalMesh(defaultMesh);
//	Mesh.SetMaterial(0,defaultMaterial0);
//	Mesh.SetPhysicsAsset(defaultPhysicsAsset);
//	Mesh.AnimSets=defaultAnimSet;
//	Mesh.SetAnimTreeTemplate(defaultAnimTree);
//}

DefaultProperties
{
	HealthMax = 300;

	RegenPerSec = 1.0f;
	Health = 175;
	Energy = 100;
	speedMod = 1.0f;

	isInvis = false;

	// abilities variable
	isHealingActivated = false;
	healingWaveActivateCost = 25.0f;
	healingWaveDrainPerSec = 5.0f;
	healingWave_lifeDrainDmg = 25;
	healingWave_healDmg = 25;
	healingWave_energyDrainDmg = 25;
	healingWave_radius = 300;

	isShieldActivated = false;
	shieldActivateCost = 25.0f;
	shieldDrainPerSec = 5.0f;
	shieldModifier = 0.5f;
	shieldRadius = 300;
	isUnderShield = false;

	///// particle effect
	//healingWaveParticle = class'UTEmit_ShockCombo'
	forcePushParticle = class'ruined_script_emit_forcepush'
	healingWaveParticle = class'ruined_script_emit_healingwave'
	shieldParticle = class'ruined_script_emit_shield'

	///// mesh and animation
	//defaultMesh=SkeletalMesh'Ruined_character.static_mesh.Ruined_character'
	//defaultMesh=SkeletalMesh'CH_LIAM_Cathode.Mesh.SK_CH_LIAM_Cathode'
	//defaultMesh=SkeletalMesh'CH_Gibs.Mesh.SK_CH_Gibs_Corrupt_Part04'
	//defaultMesh=SkeletalMesh'CH_IronGuard_Male.Mesh.SK_CH_IronGuard_MaleA'

	//defaultAnimTree=AnimTree'CH_AnimHuman_Tree.AT_CH_Human'
	//defaultAnimSet(0)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_BaseMale'
	//defaultPhysicsAsset=PhysicsAsset'CH_AnimCorrupt.Mesh.SK_CH_Corrupt_Male_Physics'

	//Begin Object Name=WPawnSkeletalMeshComponent
	//	AnimTreeTemplate=AnimTree'CH_AnimHuman_Tree.AT_CH_Human'
	//End Object

	Begin Object class=SkeletalMeshComponent Name=sMesh
        SkeletalMesh=SkeletalMesh'Ruined_Bolter.Mesh.force_field'
        Scale=3.0
	End Object
	shieldMesh = sMesh;
	//Components.Add(sMesh)
}

class ruined_script_pawn_scout extends ruined_script_pawn_base;

simulated function PostBeginPlay()
{
	local ruined_script_weapon_shotgun weap;
	weap = Spawn( class 'ruined_script_weapon_shotgun', self, , , , , );

	super.PostBeginPlay();

	if ( weap != None )
	{
		InvManager.AddInventory( weap, false );
	}
}

simulated function SetCharacterClassFromInfo(class Info)
{
	local class tmp;
	tmp = class 'ruined_script_utfamilyinfo_scout';
	super.SetCharacterClassFromInfo(tmp);
}

DefaultProperties
{
	Health = 75;
	HealthMax = 125;
	speedMod = 1.75f
}

class ruined_script_projectile_powerthrust extends UTProj_LinkPlasma;

var class ComboExplosionEffect;

function Init(vector Direction)
{
	super.Init(Direction);
	Spawn(class'ruined_script_emit_forcepush',,,Location);
}

simulated function Tick(float deltaTime)
{
	local Actor	Victims;
	local float DamageRadius1;
	local Vector HurtOrigin;
	local TraceHitInfo HitInfo;

	local Vector dir;
	local float portion;

	local Projectile P;

	DamageRadius1 = 275;
	HurtOrigin = Location;

	foreach VisibleCollidingActors( class'Actor', Victims, DamageRadius1, HurtOrigin,,,,, HitInfo )
	{
		//if ( !Victims.bWorldGeometry && (Victims != self) && (Victims != IgnoredActor) && (Victims.bCanBeDamaged || Victims.bProjTarget) )
		if ( !Victims.bWorldGeometry && (Victims != self) && (Victims != Instigator) )
		{
			//`log("actor", true, name);
			//`log(Victims.ObjectArchetype.Name, true, name);

			// force push
			Victims.TakeRadiusDamage(Instigator.Controller, 0, 1500.0f, class 'UTDmgType_LinkPlasma', 200000.0f, HurtOrigin, true, self);
		}
	}

	ForEach AllActors(class'Projectile', P)
	{
		if (P.ObjectArchetype.Class != class'ruined_script_projectile_powerthrust')
		{
			//`log("actor", true, name);
			//`log(P.ObjectArchetype.Name, true, name);

			dir = P.Location - HurtOrigin;
			portion = VSize(dir) / 1500.0f;
			if (portion < 1.0f) 			{ 				P.Velocity = P.Velocity + dir * portion * (10.0f*VSize(P.Velocity)); 			} 		} 	} 	super.Tick(deltaTime); } DefaultProperties { 	ComboExplosionEffect=class'ruined_script_emit_forcepush'; 	//ProjFlightTemplate=ParticleSystem'Ruined_Shotgun.Particles.Ruined_Shotgun_ForcePush' 	//ProjExplosionTemplate=ParticleSystem'Ruined_Shotgun.Particles.Ruined_Shotgun_ForcePush' 	ProjFlightTemplate=none; 	ProjExplosionTemplate=none; 	ExplosionSound=SoundCue'Ruined_Sounds.Cues.Ability_Proj' 	//ProjectileLightClass=class'UTGame.UTShockBallLight' 	LifeSpan=1.0 	Speed=1000 	MaxSpeed=2000 	AccelRate=0.0 	//HurtRadius(Damage, DamageRadius, MyDamageType, MomentumTransfer, AltOrigin); } 
 class ruined_script_weapon_heat_beam extends UTWeap_LinkGun; var class vehicleDmgType; var() Array EnergyConsume; var bool canFire; replication 	{ 		if ( bNetDirty ) 			vehicleDmgType; 	} /**  * Called on the LocalPlayer, Fire sends the shoot request to the server (ServerStartFire)  * and them simulates the firing effects locally.  * Call path: PlayerController::StartFire -> Pawn::StartFire -> InventoryManager::StartFire
 * Network: LocalPlayer
 */
simulated function StartFire(byte FireModeNum)
{
	if ( FireModeNum == 1 )
		FireModeNum = 0;
	else
		FireModeNum = 1;

	if ( FireModeNum == 1 )
	{
		if( Instigator == None || !Instigator.bNoWeaponFiring )
		{
			if( Role < Role_Authority )
			{
				// if we're a client, synchronize server
				ServerStartFire(FireModeNum);
			}

			// Start fire locally
			BeginFire(FireModeNum);
		}
	}
}

/**
 * This initiates the shutdown of a weapon that is firing.
 * Network: Local Player
 */

simulated function StopFire(byte FireModeNum)
{
	if ( FireModeNum == 1 )
		FireModeNum = 0;
	else
		FireModeNum = 1;

	// Locally shut down the fire sequence
	EndFire(FireModeNum);

	// Notify the server
	if( Role < Role_Authority ) 	{ 		ServerStopFire(FireModeNum); 	} } simulated function FireAmmunition() { 	local ruined_script_pawn_base holder; 	holder = ruined_script_pawn_base(Instigator); 	if (holder.hasEnergy(EnergyConsume[CurrentFireMode])) 	{ 		holder.Energy -= EnergyConsume[CurrentFireMode]; 		super.FireAmmunition(); 	} } simulated function Tick(float DeltaTime) { 	if ( Instigator != none ) 	{ 		AmmoCount = ruined_script_pawn_base(Instigator).Energy; 	} 		 	super.Tick(DeltaTime); } function ConsumeBeamAmmo(float Amount) { 	AmmoCount = ruined_script_pawn_base(Instigator).Energy; 	 	super.ConsumeBeamAmmo(Amount); 	ruined_script_pawn_base(Instigator).Energy = AmmoCount; } simulated function ProcessBeamHit(Vector StartTrace, Vector AimDir, out ImpactInfo TestImpact, float DeltaTime) { 	local float DamageAmount; 	local vector PushForce, ShotDir, SideDir; //, HitLocation, HitNormal, AttachDir; 	local UTPawn UTP; 	if (canFire && ruined_script_pawn_base(Instigator).hasEnergy(1)) 	{ 		super.ProcessBeamHit( StartTrace, AimDir, TestImpact, DeltaTime ); 		// compute damage amount 		CalcLinkStrength(); 		DamageAmount = InstantHitDamage[1]; 		UTP = UTPawn(Instigator); 		if ( UTP != None ) 		{ 			DamageAmount = DamageAmount/UTP.FireRateMultiplier; 		} 		if ( LinkStrength > 1 )
		{
			DamageAmount *= FClamp(0.75*LinkStrength, 1.5, 2.0);
		}
		SavedDamage += DamageAmount * DeltaTime;
		DamageAmount = int(SavedDamage);
		SavedAmmoUse += BeamAmmoUsePerSecond * DeltaTime;

		if (Victim.ObjectArchetype.IsA( 'ruined_script_custom_statue' ))
		{
			ConsumeBeamAmmo(SavedAmmoUse);ShotDir = Normal(TestImpact.HitLocation - Location);
			SideDir = Normal(ShotDir Cross vect(0,0,1));
			PushForce =  vect(0,0,1) + Normal(SideDir * (SideDir dot (TestImpact.HitLocation - Victim.Location)));
			PushForce *= (Victim.Physics == PHYS_Walking) ? 0.1*MomentumTransfer : DeltaTime*MomentumTransfer;
			//`log("Yeah", true, name);
			ruined_script_custom_statue(Victim).TakeDamage(DamageAmount, Instigator.Controller, TestImpact.HitLocation, PushForce, vehicleDmgType, TestImpact.HitInfo, self);
		}
	}
	else
	{
		StopFire(CurrentFireMode);
	}

}

DefaultProperties
{
	WeaponRange = 32768.0f;            // extended the range of the weapon
	WeaponLinkDistance = 32768.0f;
	BeamAmmoUsePerSecond = 5.0f;        // disable the ammo to be consume over time

	canFire = true;

	EnergyConsume[0] = 0.0f;
	EnergyConsume[1] = 0.0f;

	ShotCost(0) = 0;
	ShotCost(1) = 0;

	//vehicleDmgType=class'UTDmgType_VehicleExplosion';
	//InstantHitDamageTypes(1)=class'UTDmgType_VehicleExplosion';

	//vehicleDmgType = class 'UTDmgType_VehicleExplosion';
	vehicleDmgType = class'UTDmgType_ScorpionSelfDestruct';

	///// visual properties
	Begin Object Name=FirstPersonMesh
		SkeletalMesh=SkeletalMesh'Ruined_Heatgun.Mesh.Ruined_Heatgun'
		AnimSets(0)=AnimSet'Ruined_Heatgun.Mesh.Ruined_Heatgun_Anim'
		Materials(0)=Material'Ruined_Heatgun.Materials.Ruined_HeatGun_Mat'
		Translation=(X=10,Y=30,Z=-3)
		Rotation=(Yaw=0)
		Animations=MeshSequenceA

		Scale=1.2
		FOV=60
	End Object

	Begin Object Name=PickupMesh
		SkeletalMesh=SkeletalMesh'Ruined_Heatgun.Mesh.Ruined_Heatgun'
	End Object
	AttachmentClass=class'ruined_script_attachment_heat_beam'

	WeaponEquipSnd=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_RaiseCue'
	WeaponPutDownSnd=SoundCue'A_Weapon_Link.Cue.A_Weapon_Link_LowerCue'
	WeaponFireSnd(0)=SoundCue'Ruined_Sounds.Cues.BoltCue'
	WeaponFireSnd(1)=SoundCue'Ruined_Sounds.Cues.HeatBeamCue'

	// Try making sure that no one else can pick-up this weapon
	//
	bCanThrow = false;
}

class ruined_script_custom_statue extends FracturedStaticMeshActor;

var bool isHit;
var bool isExplode;
var(Statue) int InitStatueHealth;
var(Statue) bool isRed;
var int statueHealth;

replication
	{
		if (bNetDirty)
			statueHealth, isRed, isExplode, InitStatueHealth;
	}

client reliable function TriggerExplosion()
{
	Explode();
	isExplode = true;
}

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

	statueHealth = InitStatueHealth;
}

simulated event TakeDamage(int Damage, Controller EventInstigator, vector HitLocation, vector Momentum, class DmgType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
{
	local int i;
	local bool isHurt;

	isHurt = false;

	if(FracturedByDamageType.length == 0)
	{
		isHurt = TRUE;
	}
	else
	{
		for(i=0; i		{
			if(DmgType == FracturedByDamageType[i] &&
			  (( isRed && EventInstigator.GetTeamNum() == 1 ) ||
			  ( !isRed && EventInstigator.GetTeamNum() == 0 )))
			{
				isHurt = TRUE;
			}
		}
	}

	if ( Role == ROLE_Authority && isHurt )
	{
		statueHealth -= Damage;
	}

	if (statueHealth 	{
		statueHealth = 0;

		if ( isRed )
		{
			TriggerGlobalEventClass(class'ruined_script_red_statue_destroyed', self);
		}
		else
		{
			TriggerGlobalEventClass(class'ruined_script_blue_statue_destroyed', self);
		}

		TriggerExplosion();
	}

}

DefaultProperties
{
	FracturedByDamageType.Add(UTDmgType_VehicleExplosion);
	InitStatueHealth = 500;
	isHit = false;
	isRed = false;
	isExplode = false;
	//bDelayedStart = false;

	RemoteRole = ROLE_SimulatedProxy;
	bAlwaysRelevant = true;
}