1
0
KF2-Dev-Scripts/KFGame/Classes/KFPerk.uc
2021-03-11 22:29:08 +03:00

1507 lines
47 KiB
Ucode

//=============================================================================
// 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<PassivePerk> 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<string> ColumnOneIcons;
var array<string> ColumnTwoIcons;
var Texture2D InteractIcon;
var array<Texture2D> 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<PerkSkill> 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<BuffedPlayerInfo> BuffedPlayerInfos;
// This trigger is created to allow other players to interact with your perk abilities
var KFUsablePerkTrigger InteractionTrigger;
var const array<Name> ZedTimeModifyingStates;
var protected array<byte> BodyPartsCanStumble;
var protected array<byte> 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<KFDamageType> 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<KFWeaponDefinition> PrimaryWeaponDef;
var class<KFWeaponDefinition> SecondaryWeaponDef;
/** default starting knife */
var class<KFWeaponDefinition> KnifeWeaponDef;
/** perk specific grenade class */
var class<KFWeaponDefinition> GrenadeWeaponDef;
/** The grenade archetype for this perk. Don't set directly in defaultprops. Set the GrenadeClassName so it will be dynamically loaded */
var class<KFProj_Grenade> 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<name> BackupWeaponDamageTypeNames;
var array<class<KFWeaponDefinition> > 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<string> PrestigeRewardItemIconPaths;
/*********************************************************************************************
* 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<KFPerk> PerkClass, out byte bTierUnlocked, out int UnlockedPerkLevel);
native static final function LoadPerkData();
native static final function SaveTierUnlockToConfig(class<KFPerk> PerkClass, byte bTierUnlocked, int PerkLevel);
native final function SaveBuildToStats( Class<KFPerk> PerkClass, int NewPerkBuild );
native final function SavePerkDataToConfig( Class<KFPerk> 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<KFPerk> 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<KFPerk> GetPerkFromDamageCauser( Actor WeaponActor, class<KFPerk> 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<KFPerk> 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<KFPerk> > WeaponPerkClass, optional class<KFPerk> 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<KFDamageType> 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<DamageType> 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<KFDamageType> KFDT, class<KFPerk> 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<KFPerk> 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<KFPerk> 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<KFPerk> 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<string> PassiveValues, out array<string> 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<KFProj_Grenade>(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<Weapon>(DynamicLoadObject(GetPrimaryWeaponClassPath(), class'Class')));
}
if(KFGameInfo(WorldInfo.Game).AllowSecondaryWeapon(GetSecondaryWeaponClassPath()))
{
P.DefaultInventory.AddItem(class<Weapon>(DynamicLoadObject(GetSecondaryWeaponClassPath(), class'Class')));
}
}
else
{
P.DefaultInventory.AddItem(class<Weapon>(DynamicLoadObject(GetPrimaryWeaponClassPath(), class'Class')));
P.DefaultInventory.AddItem(class<Weapon>(DynamicLoadObject(GetSecondaryWeaponClassPath(), class'Class')));
}
P.DefaultInventory.AddItem(class<Weapon>(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<class<KFWeapon> > 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<class<KFWeapon> > 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 );
/** Allow perk to adjust damage given */
function ModifyDamageGiven( out int InDamage, optional Actor DamageCauser, optional KFPawn_Monster MyKFPM, optional KFPlayerController DamageInstigator, optional class<KFDamageType> DamageType, optional int HitZoneIdx );
function ModifyDamageTaken( out int InDamage, optional class<DamageType> DamageType, optional Controller InstigatedBy );
/** Ammunition capacity and mag count increased */
simulated function ModifyMagSizeAndNumber( KFWeapon KFW, out int MagazineCapacity, optional array< Class<KFPerk> > 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<KFPerk> > 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<KFPerk> 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> DamageType, optional byte BodyPart, optional bool bIsSprinting=false ){ return 0.f; }
function float GetStumblePowerModifier( optional KFPawn KFP, optional class<KFDamageType> DamageType, optional out float CooldownModifier, optional byte BodyPart ){ return 0.f; }
function float GetStunPowerModifier( optional class<DamageType> DamageType, optional byte HitZoneIdx ){ return 0.f; }
function bool IsStunGuaranteed( optional class<DamageType> DamageType, optional byte HitZoneIdx ){ return false; }
function float GetReactionModifier( optional class<KFDamageType> DamageType ){ return 1.f; }
simulated function float GetSnareSpeedModifier() { return 1.f; }
simulated function float GetSnarePowerModifier( optional class<DamageType> 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<KFDamageType> 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<DamageType> DT ){ return false; }
function ModifyHardAttackDamage( out int InDamage );
function ModifyLightAttackDamage( out int InDamaghe );
simulated function SetSuccessfullBlock();
simulated function SetSuccessfullParry();
function AddVampireHealth( KFPlayerController KFPC, class<DamageType> 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<KFDamageType> 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<KFDamageType> KFDT ){ return false; }
function ToxicCloudExplode( Controller Killer, Pawn ZedKilled );
/** Firebug functions */
simulated function bool IsFlarotovActive(){ return false; }
function float GetDoTScalerAdditions(class<KFDamageType> KFDT);
function bool GetFireStumble( optional KFPawn KFP, optional class<DamageType> DamageType ){ return false; }
function bool CanSpreadNapalm(){ return false; }
function bool CouldBeZedShrapnel( class<KFDamageType> 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<KFDamageType> 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> DamageType, int NumHit );
function AddToHeadShotCombo( class<KFDamageType> 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<KFWeaponDefinition> 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<EmitterCameraLensEffectBase> GetPerkLensEffect( class<KFDamageType> 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<KFWeaponDefinition> GetGrenadeWeaponDef()
{
return default.GrenadeWeaponDef;
}
/*********************************************************************************************
* @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"
}