//============================================================================= // KFPerk //============================================================================= // Base class for the KF2 perks //============================================================================= // Killing Floor 2 // Copyright (C) 2015 Tripwire Interactive LLC // - Christian "schneidzekk" Schneider //============================================================================= class KFPerk extends ReplicationInfo config(game) native nativereplication; `include(KFOnlineStats.uci) const SKILLFLAG_0 = 0x1; const SKILLFLAG_1 = 0x2; const SKILLFLAG_2 = 0x4; const SKILLFLAG_3 = 0x8; const SKILLFLAG_4 = 0x10; const SKILLFLAG_5 = 0x20; const SKILLFLAG_6 = 0x40; const SKILLFLAG_7 = 0x80; const SKILLFLAG_8 = 0x100; const SKILLFLAG_9 = 0x200; const SKILL_NONE = 0; const SKILL_1 = 1; const SKILL_2 = 2; /** Stat ID of xp value for this perk */ var private const int ProgressStatID; var private const int PerkBuildStatID; /** Skill Tiers **/ const RANK_1_LEVEL = 5; const RANK_2_LEVEL = 10; const RANK_3_LEVEL = 15; const RANK_4_LEVEL = 20; const RANK_5_LEVEL = 25; const UNLOCK_INTERVAL = 5; var const int SecondaryXPModifier[4]; /********************************************************************************************* * Localization ********************************************************************************************* */ var localized string PerkName; struct native PassivePerk { var localized string Title; var localized string Description; var string IconPath; }; var array Passives; var localized string SkillCatagories[`MAX_PERK_SKILLS]; var localized string EXPAction1; //The first line description of how specific classes get EXP var localized string EXPAction2; //The second line description of how specific classes get EXP var localized string LevelString; /** The location of this perks icon */ var Texture2D PerkIcon; var array ColumnOneIcons; var array ColumnTwoIcons; var Texture2D InteractIcon; var array PrestigeIcons; var localized string WeaponDroppedMessage; /********************************************************************************************* * Skill related vars ********************************************************************************************* */ struct native PerkSkill { var() editconst string Name; var() const float Increment; var const byte Rank; var() const float StartingValue; var() const float MaxValue; var() const float ModifierValue; var() const string IconPath; var() bool bActive; }; var private const float AssistDoshModifier; var array PerkSkills; var protected byte SelectedSkills[`MAX_PERK_SKILLS]; var() const float RegenerationInterval; // Duration till next RegenerationAmount var() const int RegenerationAmount; // Amount health given per RegenerationIntervall var float TimeUntilNextRegen; var const float BarrageDamageModifier; var const float FormidableDamageModifier; var const float VaccinationDuration; struct native BuffedPlayerInfo { var() int PreBuffValue; var() KFPawn_Human BuffedPawn; }; var array BuffedPlayerInfos; // This trigger is created to allow other players to interact with your perk abilities var KFUsablePerkTrigger InteractionTrigger; var const array ZedTimeModifyingStates; var protected array BodyPartsCanStumble; var protected array BodyPartsCanKnockDown; /********************************************************************************************* * Special abilities ********************************************************************************************* */ var bool bCanSeeCloakedZeds; var() const float SignatureDamageScale; var() const float SignatureRecoilScale; var int CurrentAbilityPoints; var byte MaxAbilityPoints; /********************************************************************************************* * Temporary abilities ********************************************************************************************* */ var bool bHasTempSkill_TacticalReload; /********************************************************************************************* * Shared Skills ********************************************************************************************* */ var() const PerkSkill TacticalReload; // Reload speed modifier Only set the StartValue var protected const class ToxicDmgTypeClass; /********************************************************************************************* * Loading ********************************************************************************************* */ var const protected byte CurrentLevel; var const protected byte CurrentPrestigeLevel; var int SavedBuild; /** Initialization */ var const bool bInitialized; /********************************************************************************************* * Inventory ********************************************************************************************* */ /** The weapon class string used to load the weapon class for this perk */ var class PrimaryWeaponDef; var class SecondaryWeaponDef; /** default starting knife */ var class KnifeWeaponDef; /** perk specific grenade class */ var class GrenadeWeaponDef; /** The grenade archetype for this perk. Don't set directly in defaultprops. Set the GrenadeClassName so it will be dynamically loaded */ var class GrenadeClass; /** The number of grenades this perk spawns with */ var int InitialGrenadeCount; /** The maximum number of grenades this perk can carry */ var int MaxGrenadeCount; /** The backup weapon damage types */ var array BackupWeaponDamageTypeNames; var array > AutoBuyLoadOutPath; /********************************************************************************************* * Player Skill Trakcing ********************************************************************************************* */ /** How much to handicap this perk when calculating the player's accuracy */ var float HitAccuracyHandicap; /** How much to handicap this perk when calculating the player's headshot accuracy */ var float HeadshotAccuracyHandicap; /********************************************************************************************* * @name Prestige Rewards ********************************************************************************************* */ var array PrestigeRewardItemIconPaths; /********************************************************************************************* * Transient vars ********************************************************************************************* */ /** Indicates if the last hit was a headshot. This is added for the crossbow to apply the stun on enemies only if hit in the head. Afflictions are calculated after adjusting the damage, so the bone is not necessary indicating a headshot anymore. */ var transient bool bWasLastHitAHeadshot; /********************************************************************************************* * Caching ********************************************************************************************* */ var KFPlayerReplicationInfo MyPRI; var KFGameInfo MyKFGI; var KFGameReplicationInfo MyKFGRI; var KFPlayerController OwnerPC; var KFPawn_Human OwnerPawn; /** Debugging */ var config bool bLogPerk; cpptext { // Override BeginPlay to do initial loading virtual void PreBeginPlay(); // Override replication for set owner / RPC calls INT* GetOptimizedRepList( BYTE* Recent, FPropertyRetirement* Retire, INT* Ptr, UPackageMap* Map, UActorChannel* Channel ); virtual void PostNetReceive(); // specific abilities virtual UBOOL UseBlueMeleeTrails() {return FALSE;} virtual UBOOL UseRedMeleeTrails() {return FALSE;} } replication { if( bNetDirty && bNetOwner ) CurrentLevel, CurrentPrestigeLevel; } /** * @brief Let's make sure we have an owner apwn * @return We have a pawn.... */ simulated final protected native function bool CheckOwnerPawn(); /********************************************************************************************* * @name Loading/Saving ********************************************************************************************* */ native final function LoadPerk(); native static final function LoadTierUnlockFromConfig(class PerkClass, out byte bTierUnlocked, out int UnlockedPerkLevel); native static final function LoadPerkData(); native static final function SaveTierUnlockToConfig(class PerkClass, byte bTierUnlocked, int PerkLevel); native final function SaveBuildToStats( Class PerkClass, int NewPerkBuild ); native final function SavePerkDataToConfig( Class PerkClass, int NewPerkBuild ); reliable server final private native event ServerSetPerkBuild( int NewPerkBuild, byte ClientLevel, byte ClientPrestigeLevel); reliable client final private native event ClientACK( bool bSuccess, byte AckLevel, Int PerkBuild ); /********************************************************************************************* * @name Static ********************************************************************************************* */ native static simulated function int GetProgressStatID(); native static simulated function int GetPerkBuildStatID(); final static function class GetPerkClass() { return default.class; } /** returns concatenated string for analytics logging */ function string DumpPerkLoadout() { local int i; local string Ret; Ret = ""; for( i=0; i<`MAX_PERK_SKILLS; i++ ) { Ret $= SelectedSkills[i]; } return Ret; } /** * @brief Grabs the AssociatedPerkClass from an actor * * @param WeaponActor Weapon or projectile * @return Weapon used */ static function KFWeapon GetWeaponFromDamageCauser( Actor WeaponActor ) { local KFWeapon KFW; if( WeaponActor != none ) { if( WeaponActor.IsA('Weapon') ) { KFW = KFWeapon(WeaponActor); } else if( WeaponActor.IsA('Projectile') ) { KFW = KFWeapon(WeaponActor.Owner); } else if( WeaponActor.IsA('KFSprayActor') ) { KFW = KFWeapon(WeaponActor.Base); } return KFW; } return none; } /** * @brief Grabs the AssociatedPerkClass from an actor * * @param WeaponActor The used weapon or projectile * @return the weapon's or projectile's perk class */ static function class GetPerkFromDamageCauser( Actor WeaponActor, class InstigatorPerkClass ) { local KFWeapon KFW; local KFProjectile KFPrj; local KFSprayActor KFSpray; KFW = KFWeapon(WeaponActor); KFPrj = KFProjectile(WeaponActor); if( WeaponActor != none && KFW == none ) { if( KFPrj != none && KFPrj.AssociatedPerkClass == none ) { KFW = KFWeapon(WeaponActor.Owner); } else if( KFPrj != none ) { return GetPerkFromProjectile( WeaponActor ); } else if( WeaponActor.IsA( 'KFSprayActor' ) ) { KFSpray = KFSprayActor(WeaponActor); if (ClassIsChildOf(KFSpray.MyDamageType, class'KFDT_Fire') || ClassIsChildOf(KFSpray.MyDamageType, class'KFDT_Microwave')) { return class'KFPerk_Firebug'; } else if (ClassIsChildOf(KFSpray.MyDamageType, class'KFDT_Freeze')) { return class'KFPerk_Survivalist'; } else if (ClassIsChildOf(KFSpray.MyDamageType, class'KFDT_Toxic')) { return class'KFPerk_FieldMedic'; } } else if( WeaponActor.IsA( 'KFDoorActor' ) ) { return class'KFPerk_Demolitionist'; } } if( KFW != none ) // avoid accessed none if killed from cheat (killzeds, etc.) { return KFW.static.GetWeaponPerkClass( InstigatorPerkClass ); } return none; } static function class GetPerkFromProjectile( Actor WeaponActor ) { local KFProjectile Proj; Proj = KFProjectile(WeaponActor); if( Proj != none ) { return Proj.default.AssociatedPerkClass; } return none; } /** * @brief Returns true if the weapon is associated with this perk * @details Uses WeaponPerkClass if we do not have a spawned weapon (such as in the trader menu) * * @param W the weapon * @param WeaponPerkClass weapon's perk class (optional) * * @return true/false */ static simulated function bool IsWeaponOnPerk( KFWeapon W, optional array < class > WeaponPerkClass, optional class InstigatorPerkClass, optional name WeaponClassName ) { if( W != none ) { return W.static.AllowedForAllPerks() || W.static.GetWeaponPerkClass( InstigatorPerkClass ) == default.Class; } else if( WeaponPerkClass.length > 0 ) { return WeaponPerkClass.Find(default.Class) != INDEX_NONE; } return false; } /** * @brief DamageType on perk? * * @param KFDT The damage type * @return true/false */ static function bool IsDamageTypeOnPerk( class KFDT ) { if( KFDT != none ) { return KFDT.default.ModifierPerkList.Find( default.class ) > INDEX_NONE; } return false; } /** * @brief Checks if a damage type is from valid perk backup weapon * * @param DT the damage type * @return true if valid damage type */ static function bool IsBackupDamageTypeOnPerk( class DT ) { if( DT != none ) { return default.BackupWeaponDamageTypeNames.Find( DT.name ) > INDEX_NONE; } return false; } /** * @brief DamageType on passed in perk? * * @param KFDT The damage type * @param PerkClass The perk we are chking for * @return true/false */ static function bool IsDamageTypeOnThisPerk( class KFDT, class PerkClass ) { if( KFDT != none ) { return KFDT.default.ModifierPerkList.Find( PerkClass ) > INDEX_NONE ; } return false; } /** * @brief Multiplies the XP to get the right amount for a certain perk * * @param XP XP modified */ static function MultiplySecondaryXPPoints( out int XP, byte Difficulty ) { XP *= default.SecondaryXPModifier[Difficulty]; } /** * @brief Return if a weapon qualifies a as backup weapon (9mm etc) * * @param KFW Weapon to check * @return true if backup weapon */ static function bool IsBackupWeapon( KFWeapon KFW ) { return KFW != none && KFW.default.bIsBackupWeapon; } /** * @brief Return if a weapon is Dual 9mm * * @param KFW Weapon to check * @return true if backup weapon */ static function bool IsDual9mm( KFWeapon KFW ) { return KFW != none && KFW.Class.Name == 'KFWeap_Pistol_Dual9mm'; } /********************************************************************************************* * @name Build / Level Management - Apply and save the updated build and level ********************************************************************************************* */ simulated function int GetCurrentPrestigeLevel() { return CurrentPrestigeLevel; } /** * @brief Gets the perk's level * @return The perk's level */ simulated native function byte GetLevel(); /** * @brief Sets a perk's level * * @param NewLevel The new level */ simulated native function SetLevel( byte NewLevel ); simulated native function byte GetPrestigeLevel(); simulated native function SetPrestigeLevel(byte NewLevel); static native final function int GetPrestigeRewardID(class PerkClass, byte NewLvl); /** Whether or not the passed in perk level is active. * Value will be 0-9, which then should be converted to * 0-4 and compared against the GRI max value. */ simulated function bool IsPerkLevelAllowed(int PerkIndex) { if (MyKFGRI != none) { return (PerkIndex / 2) <= MyKFGRI.MaxPerkLevel; } return true; } /** * @brief Callback that lets the perk know that its rank has been set or updated */ event PerkLevelUpdated() { if( MyKFGI != none ) { MyKFGI.GameConductor.UpdateAveragePerkRank(); } } /** * @brief Applies the active skills and abilities to the perk */ simulated event UpdateSkills() { local byte i, SkillIndex; // Apply perk skills for( i = 0; i < `MAX_PERK_SKILLS; i++ ) { SkillIndex = i * 2; if( SkillIndex < PerkSkills.length ) { PerkSkills[SkillIndex].bActive = SelectedSkills[i] == Skill_1; //1 PerkSkills[SkillIndex+1].bActive = SelectedSkills[i] == Skill_2;//2 } } //At this point, pending skill changes have been applied. Reset Pending Build to -1 PostSkillUpdate(); ApplySkillsToPawn(); } /** * @brief Updates the selected perk * @details Updates selected skills, packs them, sends it to the server, and saves to the cloud * * @param InSelectedSkills The skill array * @param PerkClass The perk's class */ simulated event UpdatePerkBuild( const out byte InSelectedSkills[`MAX_PERK_SKILLS], class PerkClass) { local int NewPerkBuild; if( Controller(Owner).IsLocalController() ) { PackPerkBuild( NewPerkBuild, InSelectedSkills ); ServerSetPerkBuild( NewPerkBuild, CurrentLevel, CurrentPrestigeLevel); SaveBuildToStats( PerkClass, NewPerkBuild ); SavePerkDataToConfig( PerkClass, NewPerkBuild ); } } /** * @brief Packs the slected skills into an int * * @param NewPerkBuild The new packed perk build * @param SelectedSkillsHolder Array of the perk's skills */ simulated event PackPerkBuild( out int NewPerkBuild, const out byte SelectedSkillsHolder[`MAX_PERK_SKILLS] ) { PackSkill( NewPerkBuild, SelectedSkillsHolder[0], SKILLFLAG_0, SKILLFLAG_1 ); PackSkill( NewPerkBuild, SelectedSkillsHolder[1], SKILLFLAG_2, SKILLFLAG_3 ); PackSkill( NewPerkBuild, SelectedSkillsHolder[2], SKILLFLAG_4, SKILLFLAG_5 ); PackSkill( NewPerkBuild, SelectedSkillsHolder[3], SKILLFLAG_6, SKILLFLAG_7 ); PackSkill( NewPerkBuild, SelectedSkillsHolder[4], SKILLFLAG_8, SKILLFLAG_9 ); } /** * @brief Packs 2 skills into an updated perk build int * * @param NewPerkBuild The updated perk build * @param SkillIndex Skil pair (0-4) * @param SkillFlag1 "left" skill * @param SkillFlag2 "right" skill Chris: This is kinda redundant @fixme? */ simulated event PackSkill( out int NewPerkBuild, byte SkillIndex, int SkillFlag1, int SkillFlag2 ) { if( SkillIndex == SKILL_1 ) { NewPerkBuild = NewPerkBuild | SkillFlag1; } else if( SkillIndex == SKILL_2 ) { NewPerkBuild = NewPerkBuild | SkillFlag2; } } /** * @brief Set the bytes for SelectedSkills and AbilityRanks on initialization of the perk * * @param NewPerkBuild The new passed in perk build */ simulated event SetPerkBuild( int NewPerkBuild ) { if( SavedBuild != NewPerkBuild ) { SavedBuild = NewPerkBuild; } UnpackSkill( CurrentLevel, NewPerkBuild, 0, SKILLFLAG_0, SKILLFLAG_1, SelectedSkills ); UnpackSkill( CurrentLevel, NewPerkBuild, 1, SKILLFLAG_2, SKILLFLAG_3, SelectedSkills ); UnpackSkill( CurrentLevel, NewPerkBuild, 2, SKILLFLAG_4, SKILLFLAG_5, SelectedSkills ); UnpackSkill( CurrentLevel, NewPerkBuild, 3, SKILLFLAG_6, SKILLFLAG_7, SelectedSkills ); UnpackSkill( CurrentLevel, NewPerkBuild, 4, SKILLFLAG_8, SKILLFLAG_9, SelectedSkills ); UpdateSkills(); } /** * @brief Get an unpacked array of skills * * @param PerkClass The perk class * @param NewPerkBuild passed in packed perk build * @param SelectedSkillsHolder array of skills */ simulated event GetUnpackedSkillsArray( Class PerkClass, int NewPerkBuild, out byte SelectedSkillsHolder[`MAX_PERK_SKILLS] ) { local Byte PerkLevel; if( OwnerPC == none ) { OwnerPC = KFPlayerController(Owner); } PerkLevel = OwnerPC.GetPerkLevelFromPerkList( PerkClass ); UnpackSkill( PerkLevel, NewPerkBuild, 0, SKILLFLAG_0, SKILLFLAG_1, SelectedSkillsHolder ); UnpackSkill( PerkLevel, NewPerkBuild, 1, SKILLFLAG_2, SKILLFLAG_3, SelectedSkillsHolder ); UnpackSkill( PerkLevel, NewPerkBuild, 2, SKILLFLAG_4, SKILLFLAG_5, SelectedSkillsHolder ); UnpackSkill( PerkLevel, NewPerkBuild, 3, SKILLFLAG_6, SKILLFLAG_7, SelectedSkillsHolder ); UnpackSkill( PerkLevel, NewPerkBuild, 4, SKILLFLAG_8, SKILLFLAG_9, SelectedSkillsHolder ); } /** * @brief Unpacks an individual skill from the SelectedSkills array to use * * @param PerkLevel Current perk level * @param NewPerkBuild The new perk build * @param SkillTier Skil pair (0-4) */ simulated function UnpackSkill( byte PerkLevel, int NewPerkBuild, byte SkillTier, int SkillFlag1, int SkillFlag2, out byte SelectedSkillsHolder[`MAX_PERK_SKILLS] ) { local int SkillUnlockLevel; SkillUnlockLevel = RANK_1_LEVEL + UNLOCK_INTERVAL * SkillTier; if( SkillUnlockLevel <= PerkLevel ) { if( (NewPerkBuild & SkillFlag1) > 0 ) { SelectedSkillsHolder[SkillTier] = SKILL_1; } else if( (NewPerkBuild & SkillFlag2) > 0 ) { SelectedSkillsHolder[SkillTier] = SKILL_2; } // If we've unlocked this skill, autoset it to 1 else { SelectedSkillsHolder[SkillTier] = SKILL_1; } } else { SelectedSkillsHolder[SkillTier] = SKILL_NONE; } } /** * @brief Applies skill specific changes after new skills were selected */ simulated protected event PostSkillUpdate() { local Inventory Inv; local KFWeapon KFW; local KFPawn_Human KFP; if( OwnerPC == none ) { OwnerPC = KFPlayerController(Owner); } KFP = KFPawn_Human(OwnerPC.Pawn); if( KFP != none && KFP.InvManager != none ) { for( Inv = KFP.InvManager.InventoryChain; Inv != none; Inv = Inv.Inventory ) { KFW = KFWeapon(Inv); if( KFW != none ) { // Reinitialize ammo counts KFW.ReInitializeAmmoCounts(self); } } } PerkSetOwnerHealthAndArmor( false ); ApplySkillsToPawn(); } simulated static function GetPassiveStrings( out array PassiveValues, out array Increments, byte Level ); static simulated function float GetSkillValue( const PerkSkill Skill ) { return FMin( Skill.MaxValue, Skill.StartingValue ); } static simulated function float GetPassiveValue( const out PerkSkill Skill, byte Level, optional float Divider=1.f) { return FMin(Skill.MaxValue, Skill.StartingValue + (float(Level) * Skill.Increment) / Divider); } static simulated event string GetPerkIconPath() { local string PerkIconPath; PerkIconPath = PathName(default.PerkIcon); return PerkIconPath; } static simulated event string GetPrestigeIconPath(byte PerkPrestigeLevel) { if (PerkPrestigeLevel > 0) { return "img://"$ PathName(default.PrestigeIcons[PerkPrestigeLevel - 1]); //0 based array but 0 level is ignored } return ""; } simulated final function int GetSavedBuild() { return SavedBuild; } /********************************************************************************************* * @name Spawning ********************************************************************************************* */ /** Called immediately before gameplay begins. */ simulated event PreBeginPlay() { // Set the grenade class for this perk GrenadeClass = class(DynamicLoadObject(GrenadeWeaponDef.default.WeaponClassPath, class'Class')); PerkIcon = Texture2D(DynamicLoadObject(GetPerkIconPath(), class'Texture2D')); MyKFGRI = KFGameReplicationInfo(WorldInfo.GRI); if( WorldInfo.Game != None ) { MyKFGI = KFGameInfo(WorldInfo.Game); } if( OwnerPC == none ) { OwnerPC = KFPlayerController(Owner); } if( OwnerPC != none ) { OwnerPC.SetPerkEffect( false ); } } /** On spawn, modify owning pawn based on perk selection */ function SetPlayerDefaults(Pawn PlayerPawn) { OwnerPawn = KFPawn_Human(PlayerPawn); bForceNetUpdate = TRUE; OwnerPC = KFPlayerController(Owner); if( OwnerPC != none ) { MyPRI = KFPlayerReplicationInfo(OwnerPC.PlayerReplicationInfo); } PerkSetOwnerHealthAndArmor( true ); // apply all other pawn changes ApplySkillsToPawn(); } /** On perk customization or change, modify owning pawn based on perk selection */ event NotifyPerkModified() { PostLevelUp(); } private simulated final function PerkSetOwnerHealthAndArmor( optional bool bModifyHealth ) { // don't allow clients to set health, since health/healthmax/playerhealth/playerhealthpercent is replicated if( Role != ROLE_Authority ) { return; } if( CheckOwnerPawn() ) { if( bModifyHealth ) { OwnerPawn.Health = OwnerPawn.default.Health; ModifyHealth( OwnerPawn.Health ); } OwnerPawn.HealthMax = OwnerPawn.default.Health; ModifyHealth( OwnerPawn.HealthMax ); OwnerPawn.Health = Min( OwnerPawn.Health, OwnerPawn.HealthMax ); if( OwnerPC == none ) { OwnerPC = KFPlayerController(Owner); } MyPRI = KFPlayerReplicationInfo(OwnerPC.PlayerReplicationInfo); if( MyPRI != none ) { MyPRI.PlayerHealth = OwnerPawn.Health; MyPRI.PlayerHealthPercent = FloatToByte( float(OwnerPawn.Health) / float(OwnerPawn.HealthMax) ); } OwnerPawn.MaxArmor = OwnerPawn.default.MaxArmor; ModifyArmor( OwnerPawn.MaxArmor ); OwnerPawn.Armor = Min( OwnerPawn.Armor, OwnerPawn.MaxArmor ); } } /** (Server) Modify Instigator settings based on selected perk */ function ApplySkillsToPawn() { if( CheckOwnerPawn() ) { OwnerPawn.UpdateGroundSpeed(); OwnerPawn.bMovesFastInZedTime = false; if( MyPRI == none ) { MyPRI = KFPlayerReplicationInfo(OwnerPawn.PlayerReplicationInfo); } MyPRI.bExtraFireRange = false; MyPRI.bSplashActive = false; MyPRI.bNukeActive = false; MyPRI.bConcussiveActive = false; MyPRI.PerkSupplyLevel = 0; ApplyWeightLimits(); } } function ClearPerkEffects() { if (InteractionTrigger != none) { InteractionTrigger.DestroyTrigger(); InteractionTrigger = none; } ClientClearPerkEffects(); } reliable client function ClientClearPerkEffects() { if (Role != ROLE_Authority) { if (InteractionTrigger != none) { InteractionTrigger.DestroyTrigger(); InteractionTrigger = none; } } } /** * We need to separate this from ApplySkillsToPawn() to avoid resetting weight limits (and losing weapons) * every time a skill or level is changed */ function ApplyWeightLimits() { local KFInventoryManager KFIM; KFIM = KFInventoryManager( OwnerPawn.InvManager ); if( KFIM != none ) { KFIM.MaxCarryBlocks = KFIM.default.MaxCarryBlocks; CheckForOverWeight( KFIM ); } } simulated function NotifyPawnTeamChanged() { // @note: Instigator is not replicated yet so we're using Controller.Pawn // Could add a repnotify and cache the pawn as soon we as get an instigator // Cache all the things OwnerPC = KFPlayerController(Owner); if( OwnerPC != none ) { MyPRI = KFPlayerReplicationInfo(OwnerPC.PlayerReplicationInfo); } } // Update pawn skills, abilities etc after a level up/change. simulated event PostLevelUp() { PerkSetOwnerHealthAndArmor(); PostSkillUpdate(); ApplySkillsToPawn(); } /********************************************************************************************* * @name Inventory ********************************************************************************************* */ /** @see GameInfo.AddDefaultInventory */ function AddDefaultInventory( KFPawn P ) { local KFInventoryManager KFIM; if( P != none && P.InvManager != none ) { KFIM = KFInventoryManager(P.InvManager); if( KFIM != none ) { //Grenades added on spawn KFIM.GiveInitialGrenadeCount(); } if (KFGameInfo(WorldInfo.Game) != none) { if (KFGameInfo(WorldInfo.Game).AllowPrimaryWeapon(GetPrimaryWeaponClassPath())) { P.DefaultInventory.AddItem(class(DynamicLoadObject(GetPrimaryWeaponClassPath(), class'Class'))); } if(KFGameInfo(WorldInfo.Game).AllowSecondaryWeapon(GetSecondaryWeaponClassPath())) { P.DefaultInventory.AddItem(class(DynamicLoadObject(GetSecondaryWeaponClassPath(), class'Class'))); } } else { P.DefaultInventory.AddItem(class(DynamicLoadObject(GetPrimaryWeaponClassPath(), class'Class'))); P.DefaultInventory.AddItem(class(DynamicLoadObject(GetSecondaryWeaponClassPath(), class'Class'))); } P.DefaultInventory.AddItem(class(DynamicLoadObject(GetKnifeWeaponClassPath(), class'Class'))); } } /* Returns the grenade class for this perk */ simulated function class< KFProj_Grenade > GetGrenadeClass() { return GrenadeClass; } /* Returns the primary weapon's class path for this perk */ simulated function string GetPrimaryWeaponClassPath() { return PrimaryWeaponDef.default.WeaponClassPath; } /* Returns the secondary weapon's class path for this perk */ simulated function string GetSecondaryWeaponClassPath() { return SecondaryWeaponDef.default.WeaponClassPath; } /* Returns the knife's class path for this perk */ simulated function string GetKnifeWeaponClassPath() { return KnifeWeaponDef.default.WeaponClassPath; } simulated function bool PerkNeedsTick(){ return false; } /** * @brief Checks if the carrying cpacity has changed and drops weapons if needed * * @param KFIM The inventory manager */ protected function CheckForOverWeight( KFInventoryManager KFIM ) { local int OverWeight, BestWeight; local Inventory Inv; local KFWeapon BestWeapon, KFW; local array > DroppedWeaponClasses; local string TempString; local bool bDone; if( KFIM.CurrentCarryBlocks > KFIM.MaxCarryBlocks ) { OverWeight = KFIM.CurrentCarryBlocks - KFIM.MaxCarryBlocks; for( Inv = OwnerPawn.InvManager.InventoryChain; Inv != none; Inv = Inv.Inventory ) { KFW = KFWeapon(Inv); if( KFW != none && KFW.CanThrow() ) { if( KFW.InventorySize == OverWeight ) { DroppedWeaponClasses.AddItem( KFW.class ); OwnerPawn.TossInventory( KFW ); bDone = true; } else if( (BestWeight < 1 || BestWeapon == none) || (KFW.InventorySize > Overweight && KFW.InventorySize - Overweight < BestWeight) ) { BestWeight = KFW.InventorySize - Overweight; BestWeapon = KFW; } } } if( !bDone ) { if( BestWeapon == none ) { for( Inv = OwnerPawn.InvManager.InventoryChain; Inv != none; Inv = Inv.Inventory ) { KFW = KFWeapon(Inv); if( KFW != none && KFW.CanThrow() ) { DroppedWeaponClasses.AddItem( KFW.class ); OwnerPawn.TossInventory( KFW ); if( KFIM.CurrentCarryBlocks <= KFIM.MaxCarryBlocks ) { break; } } } } else { DroppedWeaponClasses.AddItem( BestWeapon.class ); OwnerPawn.TossInventory( BestWeapon ); } } TempString = BuildDroppedMessageString( DroppedWeaponClasses ); OwnerPC.ClientMessage( Repl( WeaponDroppedMessage, "%%%%", TempString )); } } protected function string BuildDroppedMessageString( array > DroppedWeaponClasses ) { local int i; local String TempString; for( i = 0; i < DroppedWeaponClasses.Length; i++ ) { TempString = TempString @ DroppedWeaponClasses[i].default.ItemName; } return TempString; } /********************************************************************************************* * @name Gameplay ********************************************************************************************* */ /** old stuff, kept it to find old hook locations */ simulated function float GetCloakDetectionRange(){ return 0.0f; } //simulated function float GetHealthBarDetectionRange(){ return 1.0f; } simulated function float GetAwarenessDamageScale(){ return 1.0f; } simulated function float GetSuppressingFireSnareScale(){ return 1.0f; } /** shared functions */ /** Tactical Reload - Reloading rate increased */ simulated function float GetReloadRateScale(KFWeapon KFW) {return 1.f;} /** Movement - all movement speeds increased */ simulated function ModifySpeed( out float Speed ); simulated function ModifySprintSpeed( out float Speed ){ ModifySpeed( Speed ); } function FinalizeSpeedVariables(); /** Kickback - recaoil bonus */ simulated function ModifyRecoil( out float CurrentRecoilModifier, KFWeapon KFW ); /** Allows to modify the damage taken */ function ModifyDamageTaken( out int InDamage, optional class DamageType, optional Controller InstigatedBy ); /** Ammunition capacity and mag count increased */ simulated function ModifyMagSizeAndNumber( KFWeapon KFW, out int MagazineCapacity, optional array< Class > WeaponPerkClass, optional bool bSecondary=false, optional name WeaponClassname ); /** Update our weapons spare ammo count, *Use WeaponPerkClass for the trader when no weapon actually exists */ simulated function ModifySpareAmmoAmount( KFWeapon KFW, out int PrimarySpareAmmo, optional const out STraderItem TraderItem, optional bool bSecondary=false ); /** Set our weapon's spare ammo to maximum (needed another function besides ModifySpareAmmoAmount because we need to be able to specify maximum somehow) */ simulated function MaximizeSpareAmmoAmount( array< Class > WeaponPerkClass, out int PrimarySpareAmmo, int MaxPrimarySpareAmmo ); /** Update our weapons Max spare ammo count, *Use WeaponPerkClass for the trader when no weapon actually exists */ simulated function ModifyMaxSpareAmmoAmount( KFWeapon KFW, out int MaxSpareAmmo, optional const out STraderItem TraderItem, optional bool bSecondary=false ); /** Determines if a modified magazine size affects the spare ammo capacity of a weapon */ simulated function bool ShouldMagSizeModifySpareAmmo( KFWeapon KFW, optional Class WeaponPerkClass ) { return false; } /** Fortitude - max health goes up*/ function ModifyHealth( out int InHealth ); static simulated function float GetZedTimeExtension( byte Level ){ return 1.0f; } function float GetKnockdownPowerModifier( optional class DamageType, optional byte BodyPart, optional bool bIsSprinting=false ){ return 0.f; } function float GetStumblePowerModifier( optional KFPawn KFP, optional class DamageType, optional out float CooldownModifier, optional byte BodyPart ){ return 0.f; } function float GetStunPowerModifier( optional class DamageType, optional byte HitZoneIdx ){ return 0.f; } function bool IsStunGuaranteed( optional class DamageType, optional byte HitZoneIdx ){ return false; } function float GetReactionModifier( optional class DamageType ){ return 1.f; } simulated function float GetSnareSpeedModifier() { return 1.f; } simulated function float GetSnarePowerModifier( optional class DamageType, optional byte HitZoneIdx ){ return 1.f; } function GameExplosion GetExplosionTemplate(){ return none; } function bool ShouldGetAllTheXP(){ return false; } /** Support functions */ /** Welding Proficiency - faster welding/unwelding */ simulated function ModifyWeldingRate( out float FastenRate, out float UnfastenRate ); simulated function bool CanInteract( KFPawn_Human KFPH ){ return false; } simulated function Interact( KFPawn_Human KFPH ); simulated function float GetPenetrationModifier( byte Level, class DamageType, optional bool bForce ); static function float GetBarrageDamageModifier(){ return default.BarrageDamageModifier; } simulated function float GetTightChokeModifier(){ return 1.f; } /** Commando functions */ simulated function bool IsCallOutActive(){ return false; } simulated function bool IsShootAndMoveActive(){ return false; } simulated function bool HasNightVision(){ return false; } simulated protected function bool IsRapidFireActive(){ return false; } simulated function float GetZedTimeModifier( KFWeapon W ){ return 0.f; } simulated function float GetZedTimeModifierForWindUp(){ return 0.f; } simulated function ModifySpread( out float InSpread ); /** Berserker functions */ function ModifyMeleeAttackSpeed( out float InDuration, KFWeapon KFW ); function ModifyScreamEffectDuration( out float InDuration ); function bool CanNotBeGrabbed(){ return false; } function bool CanEarnSmallRadiusKillXP( class DT ){ return false; } function ModifyHardAttackDamage( out int InDamage ); function ModifyLightAttackDamage( out int InDamaghe ); simulated function SetSuccessfullBlock(); simulated function SetSuccessfullParry(); function AddVampireHealth( KFPlayerController KFPC, class DT ); function bool ShouldKnockdown(); function bool IsUnAffectedByZedTime(){ return false; } simulated event bool ShouldUseFastInstigatorDilation(KFWeapon Weap){ return false; } /** Medic functions */ function ModifyHealerRechargeTime( out float RechargeRate ); function bool ModifyHealAmount( out float HealAmount ){ return false; }; function ModifyArmor( out byte MaxArmor ); simulated function float GetArmorDiscountMod(){ return 1; } native function bool CanRepairDoors(); function bool RepairArmor( Pawn HealTarget ); function bool IsToxicDmgActive() { return false; } static function class GetToxicDmgTypeClass(){ return default.ToxicDmgTypeClass; } static function ModifyToxicDmg( out int ToxicDamage ); simulated function float GetSirenScreamStrength(){ return 1.f; } simulated function bool IsHealingSurgeActive(){ return false; } simulated function float GetSelfHealingSurgePct(){ return 0.f; } simulated function bool GetHealingSpeedBoostActive(){ return false; } simulated function bool GetHealingDamageBoostActive(){ return false; } simulated function bool GetHealingShieldActive(){ return false; } simulated function bool IsZedativeActive(){ return false; } function bool CouldBeZedToxicCloud( class KFDT ){ return false; } function ToxicCloudExplode( Controller Killer, Pawn ZedKilled ); /** Firebug functions */ simulated function bool IsFlarotovActive(){ return false; } function float GetDoTScalerAdditions(class KFDT); function bool GetFireStumble( optional KFPawn KFP, optional class DamageType ){ return false; } function bool CanSpreadNapalm(){ return false; } function bool CouldBeZedShrapnel( class KFDT ){ return false; } simulated function bool ShouldShrapnel(){ return false; } simulated function float GetSplashDamageModifier(){ return 1.f; } simulated function bool IsRangeActive(){ return false; } /** Demo functions */ simulated function bool IsOnContactActive(){ return false; } simulated function bool IsSharedExplosiveResistaneActive(){ return false; } simulated function bool ShouldSacrifice(){ return false; } simulated function bool ShouldRandSirenResist(){ return false; } simulated function bool CanExplosiveWeld(){ return false; } simulated function float GetAoERadiusModifier(){ return 1.f; } simulated function float GetAoEDamageModifier(){ return 1.f; } simulated function bool DoorShouldNuke(){ return false; } simulated function bool ShouldGetDaZeD( class DamageType ){ return false; } simulated function float GetDaZedEMPPower(){ return 0; } simulated function bool ShouldNeverDud(){ return false; } simulated function SetLastHX25NukeTime( float NewTime ); simulated function float GetLastHX25NukeTime() { return 0.f; } /** "Rack 'em Up" perk skill functions (Gunslinger, Sharpshooter) */ simulated function bool GetIsUberAmmoActive( KFWeapon KFW ){ return false; } function UpdatePerkHeadShots( ImpactInfo Impact, class DamageType, int NumHit ); function AddToHeadShotCombo( class KFDT, KFPawn_Monster KFPM ); function ResetHeadShotCombo(); simulated event bool GetIsHeadShotComboActive(){ return false; } reliable private final server event ServerResetHeadShotCombo(); simulated function ModifyRateOfFire( out float InRate, KFWeapon KFW ); simulated event float GetIronSightSpeedModifier( KFWeapon KFW ){ return 1.f; } simulated function ModifyWeaponSwitchTime( out float ModifiedSwitchTime ); simulated function bool ShouldInstantlySwitchWeapon( KFWeapon KFW ){ return false; } simulated function ModifyWeaponBopDamping( out float BobDamping, KFWeapon PawnWeapon ); simulated event float GetCameraViewShakeModifier( KFWeapon OwnerWeapon ){ return 1.f; } simulated function bool IgnoresPenetrationDmgReduction(){ return false; } /** SWAT functions */ simulated event float GetCrouchSpeedModifier( KFWeapon KFW ) { return 1.f; } simulated function bool HasHeavyArmor(){ return false; } simulated function OnBump(Actor BumpedActor, KFPawn_Human BumpInstigator, vector BumpedVelocity, rotator BumpedRotation); simulated function int GetArmorDamageAmount( int AbsorbedAmt ) { return AbsorbedAmt; } simulated event float GetZedTimeSpeedScale() { return 1.f; } static function ModifyAssistDosh( out int EarnedDosh ) { local float TempDosh; TempDosh = EarnedDosh; TempDosh *= GetAssistDoshModifer(); EarnedDosh = Round( TempDosh ); } protected static function float GetAssistDoshModifer() { return default.AssistDoshModifier; } function string GetModifierString( byte ModifierIndex ) { return ""; } function ModifyBloatBileDoT( out float DoTScaler ); /** other shared getters */ simulated function KFWeapon GetOwnerWeapon() { if( CheckOwnerPawn() ) { if( OwnerPawn.Weapon != none ) { return KFWeapon( OwnerPawn.Weapon ); } } return none; } /** * @brief Resets certain perk values on wave start/end */ function OnWaveEnded(); function OnWaveStart(); simulated function bool GetUsingTactialReload( KFWeapon KFW ) { return false; } function DrawSpecialPerkHUD( Canvas C ); /** Player entering/exiting zed time, Server only */ function NotifyZedTimeStarted(); function NotifyZedTimeEnded(); simulated event KFPawn_Human GetOwnerPawn() { local KFPawn_Human KFPH; OwnerPC = KFPlayerController(Owner); if( OwnerPC != none ) { KFPH = KFPawn_Human(OwnerPC.Pawn); if( KFPH != none ) { return KFPH; } } return none; } protected function bool HitShouldStumble( byte BodyPart ) { return BodyPartsCanStumble.Find( BodyPart ) != INDEX_NONE; } protected function bool HitShouldKnockdown( byte BodyPart ) { return BodyPartsCanKnockDown.Find( BodyPart ) != INDEX_NONE; } function bool ShouldAutosellWeapon(class DefClass) { return AutoBuyLoadOutPath.Find(DefClass) == INDEX_NONE; } event Destroyed() { ClearPerkEffects(); super.Destroyed(); } /********************************************************************************************* * @name Common Skills ********************************************************************************************* */ function TickRegen( float DeltaTime ) { local int OldHealth; local KFPlayerReplicationInfo KFPRI; local KFPlayerController KFPC; local KFPowerUp PowerUp; local bool bCannotBeHealed; local KFGameInfo GameInfo; TimeUntilNextRegen -= DeltaTime; if( TimeUntilNextRegen <= 0.f ) { if( CheckOwnerPawn() && OwnerPawn.Health < OwnerPawn.HealthMax ) { KFPC = KFPlayerController(OwnerPawn.Controller); if( KFPC != none ) { PowerUp = KFPC.GetPowerUp(); bCannotBeHealed = PowerUp != none && !PowerUp.CanBeHealed(); } GameInfo = KFGameInfo(WorldInfo.Game); bCannotBeHealed = bCannotBeHealed || (GameInfo.OutbreakEvent != none && GameInfo.OutbreakEvent.ActiveEvent.bCannotBeHealed); // If the Pawn cannot be healed return... if( bCannotBeHealed ) { return; } OldHealth = OwnerPawn.Health; `QALog( "Regeneration" @ GetPercentage(OwnerPawn.Health, Min(OwnerPawn.Health + RegenerationAmount, OwnerPawn.HealthMax)), bLogPerk ); OwnerPawn.Health = Min(OwnerPawn.Health + RegenerationAmount, OwnerPawn.HealthMax); KFPRI = KFPlayerReplicationInfo(OwnerPawn.PlayerReplicationInfo); if( KFPRI != none ) { KFPRI.PlayerHealth = OwnerPawn.Health; KFPRI.PlayerHealthPercent = FloatToByte( float(OwnerPawn.Health) / float(OwnerPawn.HealthMax) );; } if( OldHealth <= OwnerPawn.HealthMax * 0.25f && OwnerPawn.Health >= OwnerPawn.HealthMax * 0.25f ) { KFPlayerController(OwnerPawn.Controller).ReceiveLocalizedMessage(class'KFLocalMessage_Interaction', IMT_None); } if (KFGameInfo(WorldInfo.Game) != none) { KFGameInfo(WorldInfo.Game).PassiveHeal(OwnerPawn.Health - OldHealth, OldHealth, OwnerPawn.Controller, OwnerPawn); } } TimeUntilNextRegen = RegenerationInterval; } } /********************************************************************************************* * @name UI / HUD ********************************************************************************************* */ simulated function class GetPerkLensEffect( class DmgType ) { return DmgType.default.CameraLensEffectTemplate; } static simulated function Texture2d GetInteractIcon() { return default.InteractIcon; } simulated function string GetGrenadeImagePath() { return default.GrenadeWeaponDef.Static.GetImagePath(); } simulated function class GetGrenadeWeaponDef() { return default.GrenadeWeaponDef; } /** Allow perk to adjust damage given */ function ModifyDamageGiven( out int InDamage, optional Actor DamageCauser, optional KFPawn_Monster MyKFPM, optional KFPlayerController DamageInstigator, optional class DamageType, optional int HitZoneIdx ) { bWasLastHitAHeadshot = MyKFPM != none && HitZoneIdx == HZI_HEAD; } /********************************************************************************************* * @name Debug ********************************************************************************************* */ /** QA Function for logging percentage change in values */ simulated function float GetPercentage( float OriginalValue, float NewValue ) { if( OriginalValue == 0 ) { return -1; } return (NewValue - OriginalValue) / OriginalValue; } unreliable server function ServerLogPerks() { local KFPlayerController KFPC; local KFPerk MyPerk; foreach WorldInfo.AllControllers( class'KFPlayerController', KFPC ) { MyPerk = KFPC.CurrentPerk; if( MyPerk != none ) { MyPerk.LogPerkSkills(); if( !KFPC.IsLocalPlayerController() ) { MyPerk.ClientLogPerks(); } } } } unreliable client function ClientLogPerks() { LogPerkSkills(); } simulated function LogPerkSkills() { local int TierUnlockLevel; local int i; `log(" ==================================== "); `log( MyPRI.PlayerName @ PerkName @ GetLevel() ); `log(" ** SKILLS ** "); for( i = 0; i < `MAX_PERK_SKILLS; i++ ) { TierUnlockLevel = RANK_1_LEVEL + UNLOCK_INTERVAL * i; if( GetLevel() >= TierUnlockLevel ) { `log( "-Unlocked Skill Category:" @ i @ SkillCatagories[i] ); `log( "--Selected Skill:" @ SelectedSkills[i] ); } } } simulated function FormatPerkSkills() { } simulated function PlayerDied(){} DefaultProperties { bTickIsDisabled=TRUE CurrentLevel=255 PerkIcon=Texture2D'UI_PerkIcons_TEX.UI_PerkIcon_Berserker' PrestigeIcons(0)=Texture2D'UI_PerkIcons_TEX.Prestige_Rank_1' PrestigeIcons(1)=Texture2D'UI_PerkIcons_TEX.Prestige_Rank_2' PrestigeIcons(2)=Texture2D'UI_PerkIcons_TEX.Prestige_Rank_3' PrestigeIcons(3)=Texture2D'UI_PerkIcons_TEX.Prestige_Rank_4' PrestigeIcons(4)=Texture2D'UI_PerkIcons_TEX.Prestige_Rank_5' ProgressStatID=INDEX_NONE BarrageDamageModifier=1.15 FormidableDamageModifier=0.75f ToxicDmgTypeClass=class'KFDT_Toxic' RegenerationInterval=1.f RegenerationAmount=0 BackupWeaponDamageTypeNames(0)="KFDT_Ballistic_9mm"; BackupWeaponDamageTypeNames(1)="KFDT_Slashing_Knife"; // network RemoteRole=ROLE_SimulatedProxy NetUpdateFrequency=1.0 bAlwaysRelevant=FALSE bOnlyRelevantToOwner=TRUE // weapon content SecondaryWeaponDef=class'KFWeapDef_9mm' KnifeWeaponDef=class'KFWeapDef_Knife_Commando' GrenadeWeaponDef=class'KFWeapDef_Grenade_Berserker' InitialGrenadeCount=2 MaxGrenadeCount=5 SignatureDamageScale=1.f SecondaryXPModifier=1 AssistDoshModifier=1.f PrestigeRewardItemIconPaths[0]="Xmas_UI.UI_Objectives_Xmas_Krampus" bWasLastHitAHeadshot=false }