//============================================================================= // KFWeap_GrenadeLauncher_CylinderBase //============================================================================= // Cylinder based grenade launcher //============================================================================= // Killing Floor 2 // Copyright (C) 2017 Tripwire Interactive LLC //============================================================================= class KFWeap_GrenadeLauncher_CylinderBase extends KFWeap_GrenadeLauncher_Base native; var int CurrentShellSlotReloading; var bool bReloadingFromEmpty; var bool bInitialReloadIsOpenOnly; /********************************************************************************************* * @name Revolver vars ********************************************************************************************* */ // Whether this weapon is a revolver. Enables revolver functionality like rotating cylinder and noticeably-used shells var bool bRevolver; // Meshes to indicate which rounds have been fired or not var array BulletFXSocketNames; var array BulletMeshComponents; var array ShellBoneNames; var name ReloadShellSocketName; var KFBulletSkeletalMeshComponent ReloadShell; const CYLINDERSTATE_READY = 0; const CYLINDERSTATE_PENDING = 1; const CYLINDERSTATE_ROTATING = 2; var CylinderRotationInfo CylinderRotInfo; cpptext { // cylinder is incrementally rotated virtual void TickSpecial(FLOAT DeltaSeconds); }; // directly sets cylinder rotation, instead of allowing tickspecial to increment rotation native simulated function SetCylinderRotation(out CylinderRotationInfo RotInfo, float Degrees); /** Cache Anim Nodes from the tree * @note: skipped on server because AttachComponent/AttachWeaponTo is not called */ simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp) { super.PostInitAnimTree(SkelComp); if (bRevolver) { PostInitAnimTreeRevolver(SkelComp); } } /********************************************************************************************* * @name Ammo ********************************************************************************************* */ // /** // * @see Weapon::ConsumeAmmo // */ simulated function ConsumeAmmo(byte FireModeNum) { local int BulletIndex; BulletIndex = MagazineCapacity[DEFAULT_FIREMODE] - AmmoCount[DEFAULT_FIREMODE]; super.ConsumeAmmo(FireModeNum); // update bullet mesh if (BulletIndex < BulletMeshComponents.Length) { BulletMeshComponents[BulletIndex].SetBulletState(BS_Used); } // do revovler ammo before actually consuming ammo if (bRevolver && Instigator != none && Instigator.IsLocallyControlled()) { ConsumeAmmoRevolver(); } } simulated function PerformReload(optional byte FireModeNum) { if (!bInitialReloadIsOpenOnly) { bInitialReloadIsOpenOnly = true; return; } super.PerformReload(FireModeNum); BulletMeshComponents[CurrentShellSlotReloading].SetBulletState(BS_Unused); CurrentShellSlotReloading++; } simulated function UpdateShellsBaseOnAmmoCount(optional byte FireModeNum = 0) { local int i; for (i = 0; i < BulletMeshComponents.length; i++) { if (BulletMeshComponents[i].GetBulletShellState() != BS_Unused && AmmoCount[FireModeNum] > i) { BulletMeshComponents[i].SetBulletState(BS_Unused); return; } } } simulated function GetFirstEmptyShell() { } simulated function SetShellToState(int ShellIndex, byte ShellState) { switch (ShellState) { case 0: BulletMeshComponents[ShellIndex].SetBulletState(BS_Unused); break; case 1: BulletMeshComponents[ShellIndex].SetBulletState(BS_Used); break; case 2: BulletMeshComponents[ShellIndex].SetBulletState(BS_Unloaded); break; default: BulletMeshComponents[ShellIndex].SetBulletState(BS_Unused); } } /** * State Reloading * State the weapon is in when it is being reloaded (current magazine replaced with a new one, related animations and effects played). */ simulated state Reloading { ignores ForceReload, ShouldAutoReload, AllowSprinting; simulated function BeginState(name PreviousStateName) { super.BeginState(PreviousStateName); if (bRevolver) { ResetCylinder(); } } } /********************************************************************************************* * @name Revolver ********************************************************************************************* */ simulated event PostInitAnimTreeRevolver(SkeletalMeshComponent SkelComp) { local int i; CylinderRotInfo.Control = SkelControlSingleBone(SkelComp.FindSkelControl('CylinderControl')); if (CylinderRotInfo.Control != none) { CylinderRotInfo.Control.SetSkelControlActive(true); } // attach bullet meshes to correct sockets for (i = 0; i < BulletMeshComponents.Length; ++i) { BulletMeshComponents[i].SetBulletState(BS_Unused); MySkelMesh.AttachComponentToSocket(BulletMeshComponents[i], BulletFXSocketNames[i]); } MySkelMesh.AttachComponentToSocket(ReloadShell, ReloadShellSocketName); } simulated function ConsumeAmmoRevolver() { CheckCylinderRotation(CylinderRotInfo); CylinderRotInfo.State = CYLINDERSTATE_PENDING; } simulated function CheckCylinderRotation(out CylinderRotationInfo RotInfo, optional bool bResetState) { if (RotInfo.State != CYLINDERSTATE_READY) { RotateCylinder(RotInfo, true); if (bResetState) { RotInfo.State = CYLINDERSTATE_READY; } } } simulated function ANIMNOTIFY_ResetBulletMeshes() { local int i; for (i = 0; i < BulletMeshComponents.length; i++) { BulletMeshComponents[i].SetBulletState(i < MagazineCapacity[0] - AmmoCount[0] ? BS_Unloaded : BS_Unused ); } } /** Initiate cylinder rotation (rotation occurs in tickspecial in native) */ simulated function ANIMNOTIFY_RotateCylinder() { RotateCylinder(CylinderRotInfo); } /** Initiates cylinder rotation process */ simulated function RotateCylinder(out CylinderRotationInfo RotInfo, optional bool bInstant) { if (bInstant) { // don't use timer, just set rotation if (RotInfo.State == CYLINDERSTATE_PENDING) { // if we're pending, we don't have an updated rotation yet, so get a rotation IncrementCylinderRotation(RotInfo); } else { // if we're rotating already, we're going to go to a pending state after forcing rotation RotInfo.State = CYLINDERSTATE_PENDING; } SetCylinderRotation(RotInfo, RotInfo.NextDegrees); RotInfo.Timer = 0.f; } else { // use timer, rotate over time in native tickspecial RotInfo.State = CYLINDERSTATE_ROTATING; IncrementCylinderRotation(RotInfo); RotInfo.Timer = RotInfo.Time; } } simulated function IncrementCylinderRotation(out CylinderRotationInfo RotInfo) { local bool bReloading; bReloading = ReloadStatus == RS_Reloading; //if reloading reverse the rotation RotInfo.PrevDegrees = RotInfo.NextDegrees; RotInfo.NextDegrees += bReloading ? -RotInfo.Inc : RotInfo.Inc; } simulated function ResetCylinderInfo(out CylinderRotationInfo RotInfo) { RotInfo.PrevDegrees = 0; RotInfo.NextDegrees = 0; RotInfo.State = CYLINDERSTATE_READY; } simulated event OnCylinderRotationFinished(out CylinderRotationInfo RotInfo) { RotInfo.State = CYLINDERSTATE_READY; } /** Resets cylinder orientation to initial state and repositions bullet meshes to line up with their pre-reset locations */ simulated function ResetCylinder() { // reset cylinder rotation (skel control needs to be reset to initial state while bullet casings are not on screen) SetCylinderRotation(CylinderRotInfo, 0); ResetCylinderInfo(CylinderRotInfo); // now, we need to make sure the used bullets are still in the correct positions // if we fired all our ammo, it doesn't matter where the bullets are, because they're all the same (used) if (AmmoCount[DEFAULT_FIREMODE] == 0) { return; } } /** Sets all bullet casing meshes back to unused state */ simulated function ResetBulletMeshes() { local int i; return; for (i = 0; i < BulletMeshComponents.Length; ++i) { BulletMeshComponents[i].SetBulletState(BS_Unused); } } /********************************************************************************************* * State WeaponPuttingDown * Putting down weapon in favor of a new one. * Weapon is transitioning to the Inactive state. *********************************************************************************************/ simulated state WeaponPuttingDown { simulated function EndState(Name NextStateName) { super.EndState(NextStateName); CheckCylinderRotation(CylinderRotInfo, true); } } /********************************************************************************************* * @name Iron Sights / Zoom *********************************************************************************************/ /** * Adjust the FOV for the first person weapon and arms. */ simulated event SetFOV(float NewFOV) { local int i; super.SetFOV(NewFOV); for (i = 0; i < BulletMeshComponents.Length; ++i) { BulletMeshComponents[i].SetFOV(NewFOV); } if (ReloadShell != none) { ReloadShell.SetFOV(NewFOV); } } /********************************************************************************************* * @name Reload *********************************************************************************************/ simulated function InitializeReload() { bInitialReloadIsOpenOnly = false; RebuildLockedBonesForReload(); super.InitializeReload(); CheckCylinderRotation(CylinderRotInfo, true); CurrentShellSlotReloading = 0; } simulated function RebuildLockedBonesForReload() { local int i; local int NumOfShotsToReload; if (EmptyMagBlendNode != none) { EmptyMagBlendNode.SetBlendTarget(0, 0); } BonesToLockOnEmpty.length = 0; NumOfShotsToReload = MagazineCapacity[0] - AmmoCount[0]; for (i=NumOfShotsToReload; i < ShellBoneNames.length; i++) { BonesToLockOnEmpty.AddItem(ShellBoneNames[i]); } if (EmptyMagBlendNode != none && BonesToLockOnEmpty.Length > 0) { BuildEmptyMagNodeWeightList(EmptyMagBlendNode, BonesToLockOnEmpty); EmptyMagBlendNode.SetBlendTarget(1, 0); } } /** Returns animation to play based on reload type and status */ simulated function name GetReloadAnimName(bool bTacticalReload) { if (!bReloadFromMagazine) { switch (ReloadStatus) { case RS_OpeningBolt: ReloadStatus = GetNextReloadStatus(); bReloadingFromEmpty = AmmoCount[0] == 0; return bReloadingFromEmpty ? 'Reload_Empty_Open' : 'Reload_Half_Open'; case RS_Reloading: return bReloadingFromEmpty ? 'Reload_Empty_Insert' : 'Reload_Half_Insert'; case RS_ClosingBolt: return bReloadingFromEmpty ? 'Reload_Empty_Close' : 'Reload_Half_Close'; } return 'Reload_Half_Insert'; } } simulated function UnloadAllBulletMeshes() { local int i; for (i = 0; i < BulletMeshComponents.length; i++) { BulletMeshComponents[i].SetBulletState(BS_Unloaded); } } defaultproperties { ForceReloadTime=0.0f // Inventory InventoryGroup=IG_Primary GroupPriority=75 InventorySize=6 WeaponSelectTexture=Texture2D'WEP_UI_M79_TEX.UI_WeaponSelect_M79' // //NEED TO REPLACE // FOV MeshIronSightFOV=52 PlayerIronSightFOV=73 // Zooming/Position PlayerViewOffset=(X=19.0,Y=13,Z=-2) FastZoomOutTime=0.2 // Content PackageKey="M32_MGL" FirstPersonMeshName="WEP_1P_M32_MGL_MESH.Wep_1stP_M32_MGL_Rig" FirstPersonAnimSetNames(0)="WEP_1P_M32_MGL_ANIM.Wep_1stP_M32_MGL_Anim" PickupMeshName="WEP_3P_M79_MESH.Wep_m79_Pickup" //NEED TO REPLACE AttachmentArchetypeName="WEP_M32_MGL_ARCH.Wep_M32_MGL_3P" MuzzleFlashTemplateName="WEP_M79_ARCH.Wep_M79_MuzzleFlash" // Need to replace // Zooming/Position IronSightPosition=(X=0,Y=0,Z=0) // Ammo MagazineCapacity[0]=6 SpareAmmoCapacity[0]=36 InitialSpareMags[0]=9 AmmoPickupScale[0]=2.0 bCanBeReloaded=true bReloadFromMagazine=false // Recoil maxRecoilPitch=900 minRecoilPitch=775 maxRecoilYaw=500 minRecoilYaw=-500 RecoilRate=0.085 RecoilBlendOutRatio=0.35 RecoilMaxYawLimit=500 RecoilMinYawLimit=65035 RecoilMaxPitchLimit=1500 RecoilMinPitchLimit=64785 RecoilISMaxYawLimit=50 RecoilISMinYawLimit=65485 RecoilISMaxPitchLimit=500 RecoilISMinPitchLimit=65485 RecoilViewRotationScale=0.8 FallingRecoilModifier=1.5 HippedRecoilModifier=1.25 // DEFAULT_FIREMODE FireModeIconPaths(DEFAULT_FIREMODE)=Texture2D'ui_firemodes_tex.UI_FireModeSelect_Grenade' FiringStatesArray(DEFAULT_FIREMODE)=WeaponSingleFiring WeaponFireTypes(DEFAULT_FIREMODE)=EWFT_Projectile //WeaponProjectiles(DEFAULT_FIREMODE)=class'KFProj_HighExplosive_M32' FireInterval(DEFAULT_FIREMODE)=+0.25 InstantHitDamage(DEFAULT_FIREMODE)=150.0 // InstantHitDamageTypes(DEFAULT_FIREMODE)=class'KFDT_Ballistic_M32Impact' Spread(DEFAULT_FIREMODE)=0.015 FireOffset=(X=23,Y=4.0,Z=-3) // ALT_FIREMODE FiringStatesArray(ALTFIRE_FIREMODE)=WeaponSingleFiring WeaponFireTypes(ALTFIRE_FIREMODE)=EWFT_None // BASH_FIREMODE // InstantHitDamageTypes(BASH_FIREMODE)=class'KFDT_Bludgeon_M32' InstantHitDamage(BASH_FIREMODE)=26 // Fire Effects WeaponFireSnd(DEFAULT_FIREMODE)=(DefaultCue=AkEvent'WW_WEP_SA_M79.Play_WEP_SA_M79_Fire_M', FirstPersonCue=AkEvent'WW_WEP_SA_M79.Play_WEP_SA_M79_Fire_S') //@todo: add akevent when we have it WeaponDryFireSnd(DEFAULT_FIREMODE)=none // Animation bHasFireLastAnims=true // Attachments bHasIronSights=true bHasFlashlight=false AssociatedPerkClasses(0)=class'KFPerk_Demolitionist' WeaponFireWaveForm=ForceFeedbackWaveform'FX_ForceFeedback_ARCH.Gunfire.Heavy_Recoil_SingleShot' // Revolver bRevolver=true CylinderRotInfo=(Inc=72.0, Time=0.0875/*about 0.35 in the anim divided by ratescale of 4*/) ReloadShellSocketName="RW_Shell_Reload" }