Class FSTurretBase extends Decoration Abstract; var FSTurretTrigger MyTrigger; var FSTurretWeapon MyWeapon; var Weapon PreviousWeapon; var Pawn CurrentUser,ReservedUser; var vector AimTarget,ForwardDir; var transient float NextAimTime; var transient byte TraceCount; // Attachment var Mover MoverBase; var vector PositionOffset,TriggerOffset; var rotator RotationOffset,ViewOffset; var FSTurretBaseMover Updater; var FSTurretFootBase TurretBase; var() localized string OperatingString; var() name MoverBaseName; var() int YawLimit[2],PitchLimit[2],TraceHitDamage[2]; var() float AltFireZoom,RefireRate; var() class ProjectileClass; var() class TurretBaseClass; var() vector TurretBaseOffset,FireOffset; var() float FloorZHeight,FiringMissRate; var() bool bAutoDropToFloor,bTraceFire; var bool bIsAIUser,bDidAltFire,bIsZooming,bIsFiring,bHasCorrectAim; replication { // Variables the server should send to the client. reliable if ( Role==ROLE_Authority && !bNetOwner ) AimTarget; } simulated function PreBeginPlay() { local vector HL,HN; Super.PreBeginPlay(); if( bAutoDropToFloor ) { if( Trace(HL,HN,Location-vect(0,0,200),Location,false)!=None ) SetLocation(HL+vect(0,0,1)*FloorZHeight); } ForwardDir = vector(Rotation); if( MoverBaseName!='' && MoverBase==None ) { foreach AllActors(Class'Mover',MoverBase) { if( MoverBase.Name==MoverBaseName ) break; } if( MoverBase!=None ) { PositionOffset = (Location-MoverBase.Location) << MoverBase.Rotation; if( MyTrigger!=None ) TriggerOffset = (MyTrigger.Location-MoverBase.Location) << MoverBase.Rotation; RotationOffset = (Rotation-MoverBase.Rotation); SetBase(MoverBase); } } if( MoverBase!=None ) Updater = Spawn(Class'FSTurretBaseMover',Self); else RotationOffset = Rotation; if( Level.NetMode!=NM_DedicatedServer && TurretBaseClass!=None ) TurretBase = Spawn(TurretBaseClass,,,Location+(TurretBaseOffset >> Rotation),Rotation); } // Implement in subclasses for animations. function NotifyUserChange(); function Trigger( actor Other, pawn EventInstigator ) { if( CurrentUser==None && EventInstigator!=None && !EventInstigator.bDeleteMe && EventInstigator.Health>0 && FSTurretWeapon(EventInstigator.Weapon)==None && FSTurretWeapon(EventInstigator.PendingWeapon)==None ) { bIsAIUser = (PlayerPawn(EventInstigator)==None); if( MyWeapon==None || MyWeapon.bDeleteMe ) MyWeapon = Spawn(Class'FSTurretWeapon',Self); CurrentUser = EventInstigator; CurrentUser.ClientMessage(OperatingString,'Pickup'); Instigator = CurrentUser; SetOwner(CurrentUser); // Set user weapon. MyWeapon.Instigator = CurrentUser; PreviousWeapon = CurrentUser.Weapon; CurrentUser.PendingWeapon = MyWeapon; if ( CurrentUser.Weapon==None ) { if( CurrentUser.Inventory!=None ) CurrentUser.ChangedWeapon(); else { CurrentUser.PendingWeapon = None; CurrentUser.Weapon = MyWeapon; } } else CurrentUser.Weapon.PutDown(); NotifyUserChange(); } } function UnTrigger( actor Other, pawn EventInstigator ) { if( CurrentUser!=None && CurrentUser==EventInstigator ) { bIsAIUser = false; if( !CurrentUser.bDeleteMe ) { // Reset user weapon state. if( MyWeapon!=None && !MyWeapon.bDeleteMe ) MyWeapon.Instigator = None; if( CurrentUser.Weapon==MyWeapon ) CurrentUser.Weapon = None; if( PreviousWeapon!=None && !PreviousWeapon.bDeleteMe && PreviousWeapon.Owner==CurrentUser ) { CurrentUser.PendingWeapon = PreviousWeapon; if( CurrentUser.Weapon==None && CurrentUser.Inventory!=None ) CurrentUser.ChangedWeapon(); } else { CurrentUser.PendingWeapon = None; CurrentUser.SwitchToBestWeapon(); } // End zooming if( PlayerPawn(EventInstigator)!=None ) PlayerPawn(EventInstigator).EndZoom(); } SetOwner(None); CurrentUser = None; PreviousWeapon = None; NotifyUserChange(); } } function FireProjectile() { local rotator R,Tmp; local vector V; V = Location + (FireOffset >> Rotation); if( bIsAIUser && bHasCorrectAim ) { Tmp = CurrentUser.Rotation; // Hack, to prevent AI from adjusting rotation forward. CurrentUser.SetRotation(Rotation); R = CurrentUser.AdjustAim(ProjectileClass.Default.Speed,V,300,true,false); CurrentUser.SetRotation(Tmp); } else R = Rotation; if( bTraceFire ) FireTraceShot(V,Normal(vector(R)+VRand()*FiringMissRate)); else Spawn(ProjectileClass,,,V,rotator(vector(R)+VRand()*FiringMissRate)); } function FireTraceShot( vector Start, vector Dir ) { local Actor A,WallHit; local vector HL,HN; A = Trace(HL,HN,Start+Dir*8000.f,Start,true); if( A!=None ) { if( A == Level ) { if( FRand() < 0.5 ) WallHit = Spawn( class'CARWallHitEffect2',,, HL + HN * 9, Rotator(HN) ); else WallHit = Spawn( class'CARWallHitEffect',,, HL + HN * 9, Rotator(HN) ); WallHit.DrawScale -= FRand(); } else { if( !A.bIsPawn && !A.IsA( 'Carcass' ) && FRand()<0.7 ) { WallHit = spawn( class'SpriteSmokePuff',,, HL + HN * 9 ); WallHit.DrawScale -= FRand(); } A.TakeDamage(TraceHitDamage[0]+Rand(TraceHitDamage[1]-TraceHitDamage[0]),Instigator,HL,5000.0 * Dir, 'shot' ); } } else HL = Start+Dir*8000.f; if ( VSize(HL-Start)>250 && TraceCount>=2 ) { Spawn(class'CARTracer',,,Start,rotator(HL-Start)); TraceCount = 0; } else TraceCount+=Rand(3); } function Fire() { if( !bIsFiring ) { bIsFiring = true; FireProjectile(); SetTimer(RefireRate,false); } } function Timer() { bIsFiring = false; if( CurrentUser!=None && CurrentUser.bFire!=0 ) Fire(); } simulated function UpdateBasePos() { local rotator R; R = RotationOffset+MoverBase.Rotation; ForceMove((PositionOffset >> MoverBase.Rotation) + MoverBase.Location); SetRotation(ViewOffset+R); ForwardDir = vector(R); if( MyTrigger!=None ) MyTrigger.Move((TriggerOffset >> MoverBase.Rotation) + MoverBase.Location - MyTrigger.Location); if( TurretBase!=None ) { TurretBase.SetLocation((TriggerOffset >> R) + Location); TurretBase.SetRotation(R); } } simulated final function ForceMove( vector V ) // Push pawns out of the way if needed. { local Pawn P; local vector HL,HN,E; Move(V-Location); if( Location==V ) return; E.X = CollisionRadius; E.Y = CollisionRadius; E.Z = CollisionHeight; V -= Location; foreach TraceActors(Class'Pawn',P,HL,HN,V+Location,Location,E) P.MoveSmooth(V); SetLocation(V+Location); } final function ReelIn() { local vector V; V = MyTrigger.Location-CurrentUser.Location; V.Z = 0; if( VSize(V)<20.f ) CurrentUser.Acceleration = vect(0,0,0); else CurrentUser.Acceleration = Normal(V)*CurrentUser.GroundSpeed; } simulated function Tick( float Delta ) { // Serverside check if valid user. if( Level.NetMode!=NM_Client ) { if( CurrentUser!=None && (CurrentUser.bDeleteMe || CurrentUser.Health<=0 || !ActorTouches(CurrentUser,MyTrigger)) ) UnTrigger(None,CurrentUser); else if( bIsAIUser ) { if( CurrentUser.MoveTimer<=0 || CurrentUser.MoveTarget==MyTrigger ) ReelIn(); // Make it move in to center of trigger. CurrentUser.DesiredRotation = RotationOffset; if( MoverBase!=None ) CurrentUser.DesiredRotation+=MoverBase.Rotation; } } // Server/Client owner check of aiming. if( (Level.NetMode!=NM_Client && CurrentUser!=None) || (PlayerPawn(Owner)!=None && PlayerPawn(Owner).Player!=None) ) { if( !bIsAIUser ) AimTarget = TraceAimTarget(Owner.Location+vect(0,0,1)*Pawn(Owner).EyeHeight,vector(Pawn(Owner).ViewRotation)); else if( CurrentUser!=None ) { if( CurrentUser.Enemy!=None && CurrentUser.LineOfSightTo(CurrentUser.Enemy) ) { NextAimTime = Level.TimeSeconds+2.f; AimTarget = CurrentUser.Enemy.Location; } else if( NextAimTime=PlayerPawn(Owner).DefaultFOV); if( !bIsZooming ) PlayerPawn(Owner).DesiredFOV = PlayerPawn(Owner).DefaultFOV; } if( bIsZooming ) PlayerPawn(Owner).DesiredFOV = FMax(PlayerPawn(Owner).DesiredFOV-Delta*80.f,AltFireZoom); } } // Rotate towards aim target. if( Owner!=None ) RotateToTarget(Delta); } simulated final function bool ActorTouches( Actor A, Actor B ) { local vector V; V = (A.Location-B.Location); return (Abs(V.Z)<(A.CollisionHeight+B.CollisionHeight+5) && (V.X*V.X + V.Y*V.Y)32768 ) R.Yaw-=65536; R.Yaw = Clamp(R.Yaw,YawLimit[1],YawLimit[0]); R.Pitch = (R.Pitch & 65535); if( R.Pitch>32768 ) R.Pitch-=65536; R.Pitch = Clamp(R.Pitch,PitchLimit[1],PitchLimit[0]); // Delta rotate to target. ViewOffset.Yaw = FixedRotate(ViewOffset.Yaw,R.Yaw,RotationRate.Yaw*Delta); ViewOffset.Pitch = FixedRotate(ViewOffset.Pitch,R.Pitch,RotationRate.Pitch*Delta); bHasCorrectAim = (bIsAIUser && InAbs(ViewOffset.Yaw-R.Yaw,1000) && InAbs(ViewOffset.Pitch-R.Pitch,1000)); // Update view if( MoverBase==None ) SetRotation(RotationOffset+ViewOffset); else SetRotation(RotationOffset+ViewOffset+MoverBase.Rotation); } simulated final function bool InAbs( int InVal, int WithinVal ) { InVal = (InVal & 65535); if( InVal<0 ) return ((-InVal)<=WithinVal); return (InVal<=WithinVal); } simulated final function int FixedRotate( int Current, int Desired, int Delta ) { if( Delta==0 ) return Current; Current = Current & 65535; Desired = Desired & 65535; if ( Current>Desired ) { if ( Current-Desired<32768 ) return Current - Min((Current - Desired), Delta); else return Current + Min((Desired + 65536 - Current), Delta); } else if( Desired-Current< 32768 ) return Current + Min((Desired - Current), Delta); else return Current - Min((Current + 65536 - Desired), Delta); } defaultproperties { OperatingString="Operating this turret." RemoteRole=ROLE_SimulatedProxy bStatic=false bStasis=false bNoDelete=true RotationRate=(Yaw=20000,Pitch=20000) Mesh=WoodenBoxM DrawType=DT_Mesh bBlockActors=true bCollideActors=true bBlockPlayers=true bProjTarget=true bDirectional=true YawLimit(0)=16384 YawLimit(1)=-16384 PitchLimit(0)=12000 PitchLimit(1)=-12000 AltFireZoom=50 RefireRate=0.12 ProjectileClass=Class'FSTurretDAmmo' bAutoDropToFloor=true FloorZHeight=22 FiringMissRate=0.015 TraceHitDamage(0)=7 TraceHitDamage(1)=12 FireOffset=(X=22) }