//============================================================================= // KFWeap_ZedMKIII //============================================================================= // Killing Floor 2 // Copyright (C) 2022 Tripwire Interactive LLC //============================================================================= class KFWeap_ZedMKIII extends KFWeap_RifleBase; /** Number of shots required in a row before firing a rocket */ var const byte NumBulletsBeforeRocket; /** Minimum distance a target can be from the crosshair to be considered for lock on */ var const float MinTargetDistFromCrosshairSQ; /** Dot product FOV that targets need to stay within to maintain a target lock */ var const float MaxLockMaintainFOVDotThreshold; /** Time interval for updating radar positions */ var const float RadarUpdateEntitiesTime; /** Distance the radar can track enemies */ var const float MaxRadarDistance; /** Speed at which the radar moves (rad/sec) */ var const float RadarSpeed; /** */ var const byte RadarTargetSize; var transient array EnemiesInRadar; var transient byte NumShotsFired; var transient float PrevAngle; var transient bool bRequiresRadarClear; var class RadarUIClass; var KFGFxWorld_WeaponRadar RadarUI; var const float MaxTargetAngle; var transient float CosTargetAngle; var transient bool LastShotIsRocket; var WeaponFireSndInfo NormalFireSound; var WeaponFireSndInfo RocketFireSound; simulated event PostBeginPlay() { Super.PostBeginPlay(); CosTargetAngle = Cos(MaxTargetAngle * DegToRad); } simulated function AltFireMode() { } /** * Given an potential target TA determine if we can lock on to it. By default only allow locking on * to pawns. */ simulated function bool CanLockOnTo(Actor TA) { Local KFPawn PawnTarget; PawnTarget = KFPawn(TA); // Make sure the pawn is legit, isn't dead, and isn't already at full health if ((TA == None) || !TA.bProjTarget || TA.bDeleteMe || (PawnTarget == None) || (TA == Instigator) || (PawnTarget.Health <= 0) || !HasAmmo(DEFAULT_FIREMODE)) { return false; } // Make sure and only lock onto players on the same team return !WorldInfo.GRI.OnSameTeam(Instigator, TA); } /** Finds a new lock on target */ simulated function bool FindTarget( out KFPawn RecentlyLocked ) { local KFPawn P, BestTargetLock; local byte TeamNum; local vector AimStart, AimDir, TargetLoc, Projection, DirToPawn, LinePoint; local Actor HitActor; local float PointDistSQ, Score, BestScore, TargetSizeSQ; TeamNum = Instigator.GetTeamNum(); AimStart = GetSafeStartTraceLocation(); AimDir = vector( GetAdjustedAim(AimStart) ); BestScore = 0.f; foreach WorldInfo.AllPawns( class'KFPawn', P ) { if (!CanLockOnTo(P)) { continue; } // Want alive pawns and ones we already don't have locked if( P != none && P.IsAliveAndWell() && P.GetTeamNum() != TeamNum ) { TargetLoc = GetLockedTargetLoc( P ); Projection = TargetLoc - AimStart; DirToPawn = Normal( Projection ); // Filter out pawns too far from center if( AimDir dot DirToPawn < CosTargetAngle ) { continue; } // Check to make sure target isn't too far from center PointDistToLine( TargetLoc, AimDir, AimStart, LinePoint ); PointDistSQ = VSizeSQ( LinePoint - P.Location ); TargetSizeSQ = P.GetCollisionRadius() * 2.f; TargetSizeSQ *= TargetSizeSQ; // Make sure it's not obstructed HitActor = class'KFAIController'.static.ActorBlockTest(self, TargetLoc, AimStart,, true, true); if( HitActor != none && HitActor != P ) { continue; } // Distance from target has much more impact on target selection score Score = VSizeSQ( Projection ) + PointDistSQ; if( BestScore == 0.f || Score < BestScore ) { BestTargetLock = P; BestScore = Score; } } } if( BestTargetLock != none ) { RecentlyLocked = BestTargetLock; // Plays sound/FX when locking on to a new target // PlayTargetLockOnEffects(); return true; } RecentlyLocked = none; return false; } /** Adjusts our destination target impact location */ static simulated function vector GetLockedTargetLoc( Pawn P ) { // Go for the chest, but just in case we don't have something with a chest bone we'll use collision and eyeheight settings if( P.Mesh.SkeletalMesh != none && P.Mesh.bAnimTreeInitialised ) { if( P.Mesh.MatchRefBone('Spine2') != INDEX_NONE ) { return P.Mesh.GetBoneLocation( 'Spine2' ); } else if( P.Mesh.MatchRefBone('Spine1') != INDEX_NONE ) { return P.Mesh.GetBoneLocation( 'Spine1' ); } return P.Mesh.GetPosition() + ((P.CylinderComponent.CollisionHeight + (P.BaseEyeHeight * 0.5f)) * vect(0,0,1)) ; } // General chest area, fallback return P.Location + ( vect(0,0,1) * P.BaseEyeHeight * 0.75f ); } simulated function PlayFireEffects( byte FireModeNum, optional vector HitLocation ) { if (LastShotIsRocket) { super.PlayFireEffects(ALTFIRE_FIREMODE, HitLocation); WeaponPlayFireSound(WeaponFireSnd[ALTFIRE_FIREMODE].DefaultCue, WeaponFireSnd[ALTFIRE_FIREMODE].FirstPersonCue); return; } super.PlayFireEffects(FireModeNum, HitLocation); } simulated function Projectile ProjectileFire() { if (CurrentFireMode == DEFAULT_FIREMODE || CurrentFireMode == ALTFIRE_FIREMODE) { if (NumShotsFired >= NumBulletsBeforeRocket) { //WeaponFireSnd[ALTFIRE_FIREMODE]=RocketFireSound; CurrentFireMode = ALTFIRE_FIREMODE; NumShotsFired = 0; LastShotIsRocket = true; } else { //WeaponFireSnd[ALTFIRE_FIREMODE]=NormalFireSound; CurrentFireMode = DEFAULT_FIREMODE; ++NumShotsFired; LastShotIsRocket = false; } } return super.ProjectileFire(); } /** Spawn projectile is called once for each rocket fired. In burst mode it will cycle through targets until it runs out */ simulated function KFProjectile SpawnProjectile( class KFProjClass, vector RealStartLoc, vector AimDir ) { local KFProj_Rocket_ZedMKIII RocketProj; local KFPawn TargetPawn; if ( CurrentFireMode == ALTFIRE_FIREMODE ) { FindTarget(TargetPawn); RocketProj = KFProj_Rocket_ZedMKIII( super.SpawnProjectile( class(WeaponProjectiles[CurrentFireMode]) , RealStartLoc, AimDir) ); if( RocketProj != none ) { // We'll aim our rocket at a target here otherwise we will spawn a dumbfire rocket at the end of the function if ( TargetPawn != none) { //Seek to new target, then remove it RocketProj.SetLockedTarget( TargetPawn ); } } // Resetting the firemode to default. CurrentFireMode = DEFAULT_FIREMODE; return RocketProj; } return super.SpawnProjectile( KFProjClass, RealStartLoc, AimDir ); } /********************************************************************************************* * state WeaponFiring * This is the default Firing State. It's performed on both the client and the server. *********************************************************************************************/ simulated state WeaponFiring { // Reset num shots fired when stop shooting simulated event EndState( Name NextStateName) { super.EndState(NextStateName); NumShotsFired = 0; } } /********************************************************************************************* * Radar implementation *********************************************************************************************/ simulated state WeaponEquipping { simulated function BeginState(Name PreviousStateName) { super.BeginState(PreviousStateName); PrevAngle = 0.0f; if (WorldInfo.NetMode == NM_Client || WorldInfo.NetMode == NM_Standalone) { StartRadar(); } } } simulated state WeaponPuttingDown { simulated function BeginState(Name PreviousStateName) { super.BeginState(PreviousStateName); if (WorldInfo.NetMode == NM_Client || WorldInfo.NetMode == NM_Standalone) { StopRadar(); } } } simulated function StartRadar() { EnemiesInRadar.Length = 0; SetTimer(RadarUpdateEntitiesTime, true, nameof(UpdateRadarEntities)); } simulated function StopRadar() { ClearTimer(nameof(UpdateRadarEntities)); } simulated function UpdateRadarEntities() { local KFPawn_Monster KFPM; local int RadarIndex; local bool bIsAlive; bIsAlive = false; // Get nearby enemies foreach CollidingActors(class'KFPawn_Monster', KFPM, MaxRadarDistance, Location, true) { RadarIndex = FindEnemyTrackedByRadar(KFPM); bIsAlive = KFPM.IsAliveAndWell(); if (RadarIndex == INDEX_NONE) { if (bIsAlive) { EnemiesInRadar.AddItem(KFPM); } } else if(!bIsAlive) { EnemiesInRadar.RemoveItem(KFPM); } } } simulated function int FindEnemyTrackedByRadar(KFPawn_Monster KFPM) { local int i; for (i = 0; i < EnemiesInRadar.Length; ++i) { if (KFPM == EnemiesInRadar[i] ) { return i; } } return INDEX_NONE; } simulated function Tick(float Delta) { local float DistanceSqrd; local vector Distance, ScreenDirection, UILocation; local rotator ViewRotation; local int i; local array RadarElements; super.Tick(Delta); if (RadarUI != none) { if (bRequiresRadarClear) { RadarUI.Clear(); } if (EnemiesInRadar.Length == 0) { if (bRequiresRadarClear) { bRequiresRadarClear = false; } return; } ViewRotation = Rotation; ViewRotation.Yaw *= -1; ViewRotation.Pitch = 0; ViewRotation.Roll = 0; RadarElements.Length = 0; for (i = EnemiesInRadar.Length - 1; i >= 0; --i) { if (!EnemiesInRadar[i].IsAliveAndWell()) { EnemiesInRadar.Remove(i, 1); continue; } Distance = EnemiesInRadar[i].Location - Location; DistanceSqrd = VSizeSQ(Distance); if (DistanceSqrd > MaxRadarDistance * MaxRadarDistance) { EnemiesInRadar.Remove(i, 1); continue; } Distance.Z = 0; ScreenDirection = Distance >> ViewRotation; UILocation.X = ScreenDirection.Y / MaxRadarDistance; UILocation.Y = ScreenDirection.X / MaxRadarDistance; RadarElements.AddItem(UILocation); } if (RadarElements.length > 0) { RadarUI.AddRadarElements(RadarElements); bRequiresRadarClear = true; } } } /** Radar UI */ reliable client function ClientWeaponSet(bool bOptionalSet, optional bool bDoNotActivate) { local KFInventoryManager KFIM; super.ClientWeaponSet(bOptionalSet, bDoNotActivate); if (RadarUI == none && RadarUIClass != none) { KFIM = KFInventoryManager(InvManager); if (KFIM != none) { //Create the screen's UI piece RadarUI = KFGFxWorld_WeaponRadar(KFIM.GetRadarUIMovie(RadarUIClass)); } } } function ItemRemovedFromInvManager() { local KFInventoryManager KFIM; Super.ItemRemovedFromInvManager(); if (RadarUI != none) { KFIM = KFInventoryManager(InvManager); if (KFIM != none) { //Create the screen's UI piece KFIM.RemoveRadarUIMovie(RadarUI.class); RadarUI.Close(); RadarUI = none; } } } /** */ defaultproperties { // FOV MeshFOV=70 MeshIronSightFOV=30 //52 PlayerIronSightFOV=70 // Depth of field DOF_FG_FocalRadius=85 DOF_FG_MaxNearBlurSize=2.5 // Zooming/Position IronSightPosition=(X=14,Y=0.05,Z=-0.5) // x=-8,Y=0.01,Z=0.85 PlayerViewOffset=(X=22,Y=12,Z=-2.5) // Content PackageKey="ZedMKIII" FirstPersonMeshName="WEP_1P_ZEDMKIII_MESH.Wep_1stP_ZEDMKIII_Rig" FirstPersonAnimSetNames(0)="WEP_1P_ZEDMKIII_ANIM.Wep_1stP_ZEDMKIII_Anim" PickupMeshName="WEP_3P_ZEDMKIII_MESH.Wep_3rdP_ZEDMKIII_Pickup" AttachmentArchetypeName = "WEP_ZEDMKIII_ARCH.Wep_ZEDMKIII_3P" MuzzleFlashTemplateName="WEP_ZEDMKIII_ARCH.Wep_ZEDMKIII_MuzzleFlash" // Ammo MagazineCapacity[0]=50 SpareAmmoCapacity[0]=350 InitialSpareMags[0]=2 bCanBeReloaded=true bReloadFromMagazine=true bCanRefillSecondaryAmmo=0 // Recoil maxRecoilPitch=125 minRecoilPitch=100 maxRecoilYaw=120 minRecoilYaw=-100 RecoilRate=0.085 RecoilMaxYawLimit=500 RecoilMinYawLimit=65035 RecoilMaxPitchLimit=900 RecoilMinPitchLimit=65035 RecoilISMaxYawLimit=75 RecoilISMinYawLimit=65460 RecoilISMaxPitchLimit=375 RecoilISMinPitchLimit=65460 IronSightMeshFOVCompensationScale=4.0 // Inventory InventorySize=9 GroupPriority=125 WeaponSelectTexture=Texture2D'wep_ui_zedmkiii_tex.UI_WeaponSelect_ZEDMKIII' // DEFAULT_FIREMODE FireModeIconPaths(DEFAULT_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_BulletAuto' FiringStatesArray(DEFAULT_FIREMODE)=WeaponFiring WeaponFireTypes(DEFAULT_FIREMODE)=EWFT_Projectile WeaponProjectiles(DEFAULT_FIREMODE)=class'KFProj_Bullet_ZedMKIII' InstantHitDamageTypes(DEFAULT_FIREMODE)=class'KFDT_Microwave_ZedMKIII' // ALT_FIREMODE FiringStatesArray(ALTFIRE_FIREMODE)=WeaponSingleFiring WeaponFireTypes(ALTFIRE_FIREMODE)=EWFT_Projectile WeaponProjectiles(ALTFIRE_FIREMODE)=class'KFProj_Rocket_ZedMKIII' InstantHitDamage(ALTFIRE_FIREMODE)=100 InstantHitDamageTypes(ALTFIRE_FIREMODE)=class'KFDT_Ballistic_ZedMKIII_Rocket' Spread(ALTFIRE_FIREMODE)=0.025 AmmoCost(ALTFIRE_FIREMODE)=1 FireInterval(DEFAULT_FIREMODE)=0.15 // 400 RPM //0.12 // 500 RPM //+0.1 // 600 RPM Spread(DEFAULT_FIREMODE)=0.0085 PenetrationPower(DEFAULT_FIREMODE)=4.0 InstantHitDamage(DEFAULT_FIREMODE)=50.0 FireOffset=(X=30,Y=8,Z=-15) // BASH_FIREMODE InstantHitDamageTypes(BASH_FIREMODE)=class'KFDT_Bludgeon_ZedMKIII' InstantHitDamage(BASH_FIREMODE)=27 // Fire Effects NormalFireSound=(DefaultCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_Single_3P', FirstPersonCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_Single_1P') RocketFireSound=(DefaultCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_Rocket_3P', FirstPersonCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_Rocket_1P') //WeaponFireSnd(DEFAULT_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_LP_3P', FirstPersonCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_LP_1P') WeaponFireSnd(DEFAULT_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_Single_3P', FirstPersonCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_Single_1P') //WeaponFireSnd(ALTFIRE_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_Single_3P', FirstPersonCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_Single_1P') WeaponFireSnd(ALTFIRE_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_Rocket_3P', FirstPersonCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_Rocket_1P') WeaponDryFireSnd(DEFAULT_FIREMODE)=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Handling_DryFire' WeaponDryFireSnd(ALTFIRE_FIREMODE)=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Handling_DryFire' // Advanced (High RPM) Fire Effects bLoopingFireAnim(DEFAULT_FIREMODE)=true bLoopingFireSnd(DEFAULT_FIREMODE)=false //WeaponFireLoopEndSnd(DEFAULT_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_LP_End_3P', FirstPersonCue=AkEvent'WW_WEP_ZEDMKIII.Play_WEP_ZEDMKIII_Shoot_LP_End_1P') //SingleFireSoundIndex=ALTFIRE_FIREMODE // Attachments bHasIronSights=true bHasFlashlight=false AssociatedPerkClasses(0)=class'KFPerk_Demolitionist' NumBulletsBeforeRocket=6 NumShotsFired=0 MinTargetDistFromCrosshairSQ=2500.0f MaxLockMaintainFOVDotThreshold=0.36f RadarUpdateEntitiesTime=0.1f MaxRadarDistance=2000 RadarSpeed=2.0f RadarTargetSize=10.0f RadarUIClass=class'KFGFxWorld_WeaponRadar' NumBloodMapMaterials=2 bRequiresRadarClear=false MaxTargetAngle=10 LastShotIsRocket=false }