/******************************************************************************
BasiliskCannon

Creation date: 2011-08-04 14:22
Last change: $Id$
Copyright © 2011, Wormbo
Website: http://www.koehler-homepage.de/Wormbo/
Feel free to reuse this code. Send me a note if you found it helpful or want
to report bugs/provide improvements.
Please ask for permission first, if you intend to make money off reused code.
******************************************************************************/

class BasiliskCannon extends ONSWeapon;


//=============================================================================
// Imports
//=============================================================================

#exec audio import file=Sounds\EnergyBeamFire.wav
#exec audio import file=Sounds\ShockwaveCharge.wav
#exec audio import file=Sounds\ShockwaveFire.wav


//=============================================================================
// Variables
//=============================================================================

var bool bFiring, bAltWeaponFirePoint;
var float BeamFireDuration;
var float BeamRehitDelay;
var float BlastBuildUpDelay;
var name WeaponAltFireAttachmentBone;
var Sound AltFireBuildUpSound;

var array<Actor> RecentlyHit;
var array<float> RecentHitTime;

var class<Actor> BuildUpEffectClass;
var class<BasiliskHeatRayEffect> BeamEffectClass;
var BasiliskHeatRayEffect BeamEffect;


//=============================================================================
// Replication
//=============================================================================

replication
{
	reliable if (bNetOwner)
		bAltWeaponFirePoint;

	reliable if (!bNetOwner)
		bFiring;

	reliable if (bNetdirty)
		BeamEffect;
}


simulated event FlashMuzzleFlash()
{
	Super.FlashMuzzleFlash();

	if (Team == 1 && BasiliskEnergyWaveTrailer(EffectEmitter) != None)
		BasiliskEnergyWaveTrailer(EffectEmitter).SetBlueEffects();
}

simulated function DestroyEffects()
{
	EffectEmitter = None; // don't mess with it, it's the shock wave that might still be going independently

	Super.DestroyEffects();
}


function bool CanAttack(Actor Other)
{
	local float Dist, CheckDist;
	local vector HitLocation, HitNormal;
	local actor HitActor;

	if (Instigator == None || Instigator.Controller == None)
		return false;

	// check that target is within range
	Dist = VSize(Instigator.Location - Other.Location);
	if (Dist > MaxRange())
		return false;

	// check that can see target
	if (!Instigator.Controller.LineOfSightTo(Other)) {
		// might still decide to attack if known to be in shockwave range
		CheckDist = FMin(Dist, 2000.0);
		if (Dist < CheckDist)
			return Pawn(Other) == None || Pawn(Other).Controller == None || !Instigator.Controller.SameTeamAs(Pawn(Other).Controller);
		return false;
	}

	// check that would hit target, and not a friendly
	CalcWeaponFire();
	HitActor = Trace(HitLocation, HitNormal, Other.Location + Other.CollisionHeight * vect(0,0,0.8), WeaponFireLocation, true);

	return HitActor == None || HitActor == Other || Pawn(HitActor) == None || Pawn(HitActor).Controller == None || !Instigator.Controller.SameTeamAs(Pawn(HitActor).Controller);
}


function byte BestMode()
{
	local vector dir;

	if (Instigator.Controller != None && Instigator.Controller.Target != None) {
		// maybe pick shockwave instead of beam
		dir = Instigator.Controller.Target.Location - Location + 0.5 * (Instigator.Controller.Target.Velocity - Instigator.Velocity);
		return int(VSize(dir) < 1500 + FRand() * 500);
	}
	return 0;
}

function TraceFire(Vector Start, Rotator Dir)
{
	local Vector X, End, HitLocation, HitNormal;
	local Actor Other;
	local ONSWeaponPawn WeaponPawn;
	local Vehicle VehicleInstigator;
	local int Damage, i;

	MaxRange();

	if (bDoOffsetTrace) {
		WeaponPawn = ONSWeaponPawn(Owner);
		if (WeaponPawn != None && WeaponPawn.VehicleBase != None)
		{
			if (!WeaponPawn.VehicleBase.TraceThisActor(HitLocation, HitNormal, Start, Start + vector(Dir) * (WeaponPawn.VehicleBase.CollisionRadius * 1.5)))
				Start = HitLocation;
		}
		else if (!Owner.TraceThisActor(HitLocation, HitNormal, Start, Start + vector(Dir) * (Owner.CollisionRadius * 1.5)))
			Start = HitLocation;
	}

	X = Vector(Dir);
	End = Start + TraceRange * X;

	//skip past vehicle driver
	VehicleInstigator = Vehicle(Instigator);
	if (VehicleInstigator != None && VehicleInstigator.Driver != None) {
		VehicleInstigator.Driver.bBlockZeroExtentTraces = false;
		Other = Trace(HitLocation, HitNormal, End, Start, true);
		VehicleInstigator.Driver.bBlockZeroExtentTraces = true;
	}
	else {
		Other = Trace(HitLocation, HitNormal, End, Start, True);
	}

	for (i = RecentlyHit.Length - 1; i >= 0; i--) {
		if (Level.TimeSeconds - RecentHitTime[i] > BeamRehitDelay) {
			RecentHitTime.Remove(i, 1);
			RecentlyHit.Remove(i, 1);
		}
		else if (Other == RecentlyHit[i])
			break;
	}
	if (i == -1) {
		if (Other != None && Other != Instigator) {
			if (!Other.bWorldGeometry) {
				Damage = DamageMin + Rand(DamageMax - DamageMin);
				if (Vehicle(Other) != None || Pawn(Other) == None) {
					HitCount++;
					LastHitLocation = HitLocation;
					SpawnHitEffects(Other, HitLocation, HitNormal);
				}
				if (!Other.bStatic)
					Other.TakeDamage(Damage, Instigator, HitLocation, Momentum*X, DamageType);
				HitNormal = vect(0,0,0);
			}
			else {
				HitCount++;
				LastHitLocation = HitLocation;
				SpawnHitEffects(Other, HitLocation, HitNormal);
			}
			// remember recent hit
			if (Other != None) {
				RecentlyHit[RecentlyHit.Length] = Other;
				RecentHitTime[RecentHitTime.Length] = Level.TimeSeconds;
			}
		}
		else {
			HitLocation = End;
			HitNormal = Vect(0,0,0);
			HitCount++;
			LastHitLocation = HitLocation;
		}
		NetUpdateTime = Level.TimeSeconds - 1;
	}

	if (BeamEffect != None) {
		if (Other != None)
			BeamEffect.EndEffect = HitLocation;
		else
			BeamEffect.EndEffect = End;
		BeamEffect.bHitSomething = (Other != None);
	}
}


simulated function TraceBeam(Vector Start, Rotator Dir)
{
	local Vector X, End, HitLocation, HitNormal;
	local Actor Other;
	local ONSWeaponPawn WeaponPawn;
	local Vehicle VehicleInstigator;

	MaxRange();

	if (bDoOffsetTrace) {
		WeaponPawn = ONSWeaponPawn(Owner);
		if (WeaponPawn != None && WeaponPawn.VehicleBase != None)
		{
			if (!WeaponPawn.VehicleBase.TraceThisActor(HitLocation, HitNormal, Start, Start + vector(Dir) * (WeaponPawn.VehicleBase.CollisionRadius * 1.5)))
				Start = HitLocation;
		}
		else if (!Owner.TraceThisActor(HitLocation, HitNormal, Start, Start + vector(Dir) * (Owner.CollisionRadius * 1.5)))
			Start = HitLocation;
	}

	X = Vector(Dir);
	End = Start + TraceRange * X;

	//skip past vehicle driver
	VehicleInstigator = Vehicle(Instigator);
	if (VehicleInstigator != None && VehicleInstigator.Driver != None) {
		VehicleInstigator.Driver.bBlockZeroExtentTraces = false;
		Other = Trace(HitLocation, HitNormal, End, Start, true);
		VehicleInstigator.Driver.bBlockZeroExtentTraces = true;
	}
	else {
		Other = Trace(HitLocation, HitNormal, End, Start, True);
	}

	if (BeamEffect != None) {
		if (Other != None)
			BeamEffect.EndEffect = HitLocation;
		else
			BeamEffect.EndEffect = End;
		BeamEffect.bHitSomething = (Other != None && Other.bWorldGeometry);
	}
}

simulated function float ChargeBar()
{
	if (bIsAltFire) {
		if (FireCountDown > AltFireInterval - BlastBuildUpDelay)
			return FClamp((FireCountDown - (AltFireInterval - BlastBuildUpDelay)) / BlastBuildUpDelay, 0.0, 0.999);
		else
			return FClamp(1 - FireCountDown / (AltFireInterval - BlastBuildUpDelay), 0.0, 0.999);
	}
	else {
		if (FireCountdown > FireInterval - BeamFireDuration)
			return FClamp((FireCountDown - (FireInterval - BeamFireDuration)) / BeamFireDuration, 0.0, 0.999);
		else
			return FClamp(1 - FireCountDown / (FireInterval - BeamFireDuration), 0.0, 0.999);
	}
}


simulated event PostNetReceive()
{
	if (bAltWeaponFirePoint) {
		WeaponFireAttachmentBone = WeaponAltFireAttachmentBone;
	}
	else {
		WeaponFireAttachmentBone = default.WeaponFireAttachmentBone;
	}
}


function Projectile SpawnProjectile(class<Projectile> ProjClass, bool bAltFire)
{
	local Projectile P;

	P = Super.SpawnProjectile(ProjClass, bAltFire);
	if (BasiliskEnergyWave(P) != None)
		BasiliskEnergyWave(P).SetBlueEffects(Team == 1);

	return P;
}


simulated function ClientTrigger()
{
	local Actor BuildUpEffect;

	BuildUpEffect = Spawn(BuildUpEffectClass, Self);
	if (BuildUpEffect != None)
		AttachToBone(BuildUpEffect, WeaponAltFireAttachmentBone);
}

simulated state InstantFireMode
{
	function Fire(Controller C)
	{
		bFiring = True;
		PlayOwnedSound(FireSoundClass, SLOT_None, FireSoundVolume/255.0,, FireSoundRadius, FireSoundPitch, False);
		GotoState(, 'FireBeam');
		NetUpdateTime = Level.TimeSeconds - 1;
	}

	simulated function Tick(float DeltaTime)
	{
		Global.Tick(DeltaTime);

		if (bFiring) {
			CalcWeaponFire();
			if (Role == ROLE_Authority && BeamEffect == None) {
				BeamEffect = Spawn(BeamEffectClass, Instigator,, WeaponFireLocation, WeaponFireRotation);
				if (BeamEffect != None && Team < 2)
					BeamEffect.LinkColor = Team + 1;
			}
			else if (BeamEffect != None) {
				BeamEffect.SetLocation(WeaponFireLocation);
				BeamEffect.SetRotation(WeaponFireRotation);
			}

			if (Role == ROLE_Authority)
				TraceFire(WeaponFireLocation, WeaponFireRotation);
			else
				TraceBeam(WeaponFireLocation, WeaponFireRotation);
		}
		else if (Role == ROLE_Authority && BeamEffect != None)
			BeamEffect.Destroy();
	}

	simulated function SpawnHitEffects(actor HitActor, vector HitLocation, vector HitNormal)
	{
		local PlayerController PC;

		PC = Level.GetLocalPlayerController();
		if (PC != None && ((Instigator != None && Instigator.Controller == PC) || VSize(PC.ViewTarget.Location - HitLocation) < 5000)) {
			// TODO - impact effects
		}
	}

	function AltFire(Controller C)
	{
		local Actor BuildUpEffect;

		bAltWeaponFirePoint = True;
		WeaponFireAttachmentBone = WeaponAltFireAttachmentBone;
		PlayOwnedSound(AltFireBuildUpSound, SLOT_None, AltFireSoundVolume/255.0,, AltFireSoundRadius, FireSoundPitch, False);
		SetTimer(BlastBuildUpDelay, False);
		NetUpdateTime = Level.TimeSeconds - 1;
		bClientTrigger = !bClientTrigger;
		if (Level.NetMode != NM_DedicatedServer) {
			BuildUpEffect = Spawn(BuildUpEffectClass, Self);
			if (BuildUpEffect != None)
				AttachToBone(BuildUpEffect, WeaponAltFireAttachmentBone);
		}
	}

	simulated function Timer()
	{
		CalcWeaponFire();

		if (Role == ROLE_Authority) {
			SpawnProjectile(AltFireProjectileClass, True);
		}
		else if (Instigator.IsLocallyControlled()) {
			PlayOwnedSound(AltFireSoundClass, SLOT_None, 2.0,, AltFireSoundRadius, FireSoundPitch, False);
			FlashMuzzleFlash();
		}
		bAltWeaponFirePoint = False;
		WeaponFireAttachmentBone = default.WeaponFireAttachmentBone;
	}

	simulated event OwnerEffects()
	{
		if (!bIsRepeatingFF) {
			if (bIsAltFire)
				ClientPlayForceFeedback( AltFireForce );
			else
				ClientPlayForceFeedback( FireForce );
		}
		ShakeView();

		if (Role < ROLE_Authority) {
			if (bIsAltFire)
				FireCountdown = AltFireInterval;
			else
				FireCountdown = FireInterval;

			AimLockReleaseTime = Level.TimeSeconds + FireCountdown * FireIntervalAimLock;

			if (AmbientEffectEmitter != None)
				AmbientEffectEmitter.SetEmitterStatus(true);

			// Play firing noise
			if (!bAmbientFireSound) {
				if (bIsAltFire)
					PlayOwnedSound(AltFireBuildUpSound, SLOT_None, FireSoundVolume/255.0,, AltFireSoundRadius,, false);
				else
					PlayOwnedSound(FireSoundClass, SLOT_None, FireSoundVolume/255.0,, FireSoundRadius,, false);
			}
		}

		if (bIsAltFire) {
			bAltWeaponFirePoint = True;
			WeaponFireAttachmentBone = WeaponAltFireAttachmentBone;
			SetTimer(BlastBuildUpDelay, False);
		}
		else {
			bFiring = True;
			GotoState(, 'FireBeam');
		}
	}

	simulated function EndState()
	{
		bFiring = False;
	}

FireBeam:
	Sleep(BeamFireDuration);
	bFiring = False;
	NetUpdateTime = Level.TimeSeconds - 1;
}



//=============================================================================
// Default values
//=============================================================================

defaultproperties
{
	Mesh=Mesh'ONSBPAnimations.ShockTankCannonMesh'
	RedSkin=Shader'BasiliskRed'
	BlueSkin=Shader'BasiliskBlue'
	DrawScale=0.8
	YawBone="8WheelerTop"
	PitchBone="TurretAttach"

	bNetNotify = True

	Begin Object Class=ProceduralSound Name=EnergyBeamFireProc
		BaseSound = EnergyBeamFire
		PitchModification = -2.5
		PitchVariance     = 5
	End Object

	Begin Object Class=ProceduralSound Name=ShockwaveChargeProc
		BaseSound = ShockwaveCharge
		PitchModification = -2.5
		PitchVariance     = 5
	End Object

	Begin Object Class=ProceduralSound Name=ShockwaveFireProc
		BaseSound = ShockwaveFire
		PitchModification = -2.5
		PitchVariance     = 5
	End Object

	WeaponFireAttachmentBone="FirePoint"
	bInstantFire=True
	FireInterval=3.5
	BeamFireDuration = 2.0
	BeamRehitDelay = 0.4
	FireSoundClass=Sound'EnergyBeamFireProc'
	BeamEffectClass=class'BasiliskHeatRayEffect'
	DamageType=class'DamTypeBasiliskBeam'
	DamageMin=66
	DamageMax=67
	Momentum=10000.0
	TraceRange=20000.0
	FireSoundVolume=512.0
	bShowChargingBar=True

	WeaponAltFireAttachmentBone="ElectroGun"
	AltFireProjectileClass=class'BasiliskEnergyWave'
	AltFireInterval=2.0
	AltFireBuildUpSound=Sound'ShockwaveChargeProc'
	AltFireSoundClass=Sound'ShockwaveFireProc'
	BlastBuildUpDelay=0.5
	BuildUpEffectClass=class'BasiliskEnergyWaveChargeEffect'

	YawStartConstraint=0
	YawEndConstraint=65535
	PitchUpLimit=18000
	PitchDownLimit=58000
	bAimable=True
	RotationsPerSecond=0.15
	bForceSkelUpdate=True
	EffectEmitterClass=class'BasiliskEnergyWaveTrailer'
	AIInfo(0)=(bLeadTarget=true,bTrySplash=false,WarnTargetPct=0.75,RefireRate=0.5)
	AIInfo(1)=(bLeadTarget=true,bTrySplash=false,WarnTargetPct=0.75,RefireRate=0.5)
}

