Recently I'm receiving more and more requests about this, so I decided to write a tutorial.
The following steps allow your perk to benefit of all ScrN features, such as split perk progression to experience and bonus levels, support custom weapons by adding perk bonuses via config file, configurable spawn inventory etc.
Note that it will make your perk permanently linked to ScrnBalanceSrv, so it won't be possible to use on pure ServerPerks server, without ScrnBalance mutator.
I assume that you already have built the perk, which extends SRVeterancyTypes. Or at least you know how to do it. If not, please read Awesome Gartley's Tutorial first.
As an example I'm posting the code of ScrnVetBruteGunnerPNW - special ScrN Edition of original Guardianknight's perk:
And default damage types:
The following steps allow your perk to benefit of all ScrN features, such as split perk progression to experience and bonus levels, support custom weapons by adding perk bonuses via config file, configurable spawn inventory etc.
Note that it will make your perk permanently linked to ScrnBalanceSrv, so it won't be possible to use on pure ServerPerks server, without ScrnBalance mutator.
I assume that you already have built the perk, which extends SRVeterancyTypes. Or at least you know how to do it. If not, please read Awesome Gartley's Tutorial first.
- Add ScrnBalanceSrv to EditPackages in KillingFloor.ini before the package with your perk.
- Extend perk class from ScrnVeterancyTypes.
- Replace all occurrences of "KFPRI.ClientVeteranSkillLevel" with "GetClientVeteranSkillLevel(KFPRI)". Use replace feature of your text editor (try Ctrl+H or Ctrl+R) to do it quickly.
- Add ClassIsInArray() to each function to enable custom weapon support. List of bonus arrays:
- PerkedWeapons - weapon class is added to it, if server admin adds "W" bonus switch. Used for Reload, Recoil and FireRate bonuses.
- PerkedDamTypes - damage type is added to it, if server admin adds "P" or "S" bonus switches. Used for damage bonus.
- PerkedPickups - pickup class is added to it, if server admin adds "$" bonus switch. Used for discounts.
- PerkedAmmo - ammo class is added to it, if server admin adds "A" or "B" bonus switches. Used for various ammo bonuses (total ammo, magazine size etc.)
- SpecialWeapons - pickup class is added to it, if server admin adds "*" bonus switch.
- Delete AddDefaultInventory() function. This enables configurable SpawnInventory.
- Replace functions:
GetMagCapacityMod() with GetMagCapacityModStatic(),
GetReloadSpeedModifier() with GetReloadSpeedModifierStatic(),
GetFireSpeedMod() with GetFireSpeedModStatic()
for correct weapon stat display in the Trader Menu. The only difference between those functions is that they take "class<KFWeapon>" instead of "KFWeapon" argument, so they can be used without weapon instances.
- Assign DefaultDamageType and DefaultDamageTypeNoBonus in defaultproperties. Those damage types are used to allow server admin moving custom weapons to your perk. You will probably need to create a new damage type class for DefaultDamageTypeNoBonus, which allows perk progression, but doesn't add damage bonus.
- Fill OnHUDIcons array in defaultproperties. You can design own HUD icon for each 5 levels, or just use color modifiers.
As an example I'm posting the code of ScrnVetBruteGunnerPNW - special ScrN Edition of original Guardianknight's perk:
Spoiler!
Code:
class ScrnVetBruteGunnerPerk extends [COLOR="Lime"]ScrnVeterancyTypes[/COLOR]
abstract;
#exec obj load file="BruteGunnerPerkIcons.utx"
// returns perk specific stat values
static function int GetStatValueInt(ClientPerkRepLink StatOther, byte ReqNum)
{
return StatOther.GetCustomValueInt(Class'BruteGunnerPerkProg');
}
static function AddCustomStats( ClientPerkRepLink Other )
{
[COLOR="lime"]super.AddCustomStats(Other);[/COLOR] //init achievements
Other.AddCustomValue(Class'BruteGunnerPerkProg');
}
static function int AddDamage(KFPlayerReplicationInfo KFPRI, KFMonster Injured, KFPawn DamageTaker, int InDamage, class<DamageType> DmgType)
{
[COLOR="lime"] if ( DmgType == default.DefaultDamageTypeNoBonus )
return InDamage;[/COLOR]
[COLOR="lime"] if ( ClassIsChildOf(DmgType, default.DefaultDamageType)
|| ClassIsInArray(default.PerkedDamTypes, DmgType) // check damage type list of custom weapons
)[/COLOR]
{
if ( [color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color] == 0 )
InDamage *= 1.05;
else if ( [color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color] > 6 )
InDamage *= (1.50 + 0.05*([color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color]-6));
else
InDamage *= (1.00 + 0.10*fmin(5, [color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color])); // Up to 50% increase in Damage with smaller guns
// if ( Injured != none && Injured.default.HealthMax >= 1000 )
// InDamage *= 0.75; //25% damage reduction on big zeds
}
return InDamage;
}
static function int AddCarryMaxWeight(KFPlayerReplicationInfo KFPRI)
{
if ( [color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color] <= 6 )
return min([color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color]*2, 10); // 2 slots per level, up to 25 @ level 5-6
return 7 + [color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color]/2; // 1 extra slot per 2 levels above 6
}
static function float AddExtraAmmoFor(KFPlayerReplicationInfo KFPRI, Class<Ammunition> AmmoType)
{
if ( AmmoType == Class'ScrnBruteGunnerPNW.BruteAK47Ammo'
|| AmmoType == Class'ScrnBruteGunnerPNW.BruteSA80LSWAmmo'
|| AmmoType == Class'ScrnBruteGunnerPNW.BruteRPK47Ammo'
|| AmmoType == Class'ScrnBruteGunnerPNW.BrutePKMAmmo'
|| AmmoType == Class'ScrnBruteGunnerPNW.BruteM249Ammo'
|| AmmoType == Class'ScrnBruteGunnerPNW.BruteM41AAmmo'
|| AmmoType == Class'ScrnBruteGunnerPNW.BruteChainGunAmmo'
|| AmmoType == Class'ScrnBruteGunnerPNW.BruteAUG_A1ARAmmo'
|| AmmoType == Class'ScrnBruteGunnerPNW.StingerAmmo'
|| AmmoType == Class'ScrnBruteGunnerPNW.BruteThompsonAmmo'
|| [COLOR="lime"]ClassIsInArray(default.PerkedAmmo, AmmoType)[/COLOR] )
{
if ( [color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color] > 6 )
return 2.0 + 0.10*([color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color]-6);
else
return 1.0 + fmin(1.0, 0.20*[color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color]);
}
return 1.0;
}
static function float GetMagCapacityModStatic(KFPlayerReplicationInfo KFPRI, class<KFWeapon> Other)
{
return AddExtraAmmoFor(KFPRI, Other.default.FiremodeClass[0].default.AmmoClass);
}
static function float GetAmmoPickupMod(KFPlayerReplicationInfo KFPRI, KFAmmunition Other)
{
return AddExtraAmmoFor(KFPRI, Other.class);
}
static function float ModifyRecoilSpread(KFPlayerReplicationInfo KFPRI, WeaponFire Other, out float Recoil)
{
switch ( [color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color] ) {
case 0:
Recoil = 1.0;
break;
case 1:
Recoil = 0.90;
break;
case 2:
Recoil = 0.85;
break;
case 3:
Recoil = 0.80;
break;
case 4:
Recoil = 0.70;
break;
case 5:
Recoil = 0.60;
break;
default:
Recoil = 0.50;
}
return Recoil;
}
// I'm still thinking it'd better to stick with constant value, e.g. 20%
// Btw, level 8 has no speed penalty at all
// -- PooSH
static function float GetMovementSpeedModifier(KFPlayerReplicationInfo KFPRI, KFGameReplicationInfo KFGRI)
{
return 0.90;
}
// Change the cost of particular items
static function float GetCostScaling(KFPlayerReplicationInfo KFPRI, class<Pickup> Item)
{
if ( Item == class'ScrnBruteGunnerPNW.BruteAK47Pickup'
|| Item == class'ScrnBruteGunnerPNW.BruteSA80LSWPickup'
|| Item == class'ScrnBruteGunnerPNW.BruteRPK47Pickup'
|| Item == class'ScrnBruteGunnerPNW.BrutePKMPickup'
|| Item == class'ScrnBruteGunnerPNW.BruteAUG_A1ARPickup'
|| Item == class'ScrnBruteGunnerPNW.BruteM249Pickup'
|| Item == class'ScrnBruteGunnerPNW.BruteM41APickup'
|| Item == class'ScrnBruteGunnerPNW.BruteChainGunPickup'
|| Item == class'ScrnBruteGunnerPNW.StingerPickup'
|| Item == class'ScrnBruteGunnerPNW.BruteThompsonPickup'
|| [COLOR="lime"]ClassIsInArray(default.PerkedPickups, Item)[/COLOR] )
{
if ( [color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color] <= 6 )
return 0.9 - 0.10 * float([color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color]); // 10% perk level up to 6
else
return fmax(0.1, 0.3 - (0.05 * float([color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color]-6))); // 5% post level 6
}
return 1.0;
}
// Give Extra Items as default
static function AddDefaultInventory(KFPlayerReplicationInfo KFPRI, Pawn P)
{
[COLOR="lime"] if ( default.DefaultInventory.length > 0 )
super.AddDefaultInventory(KFPRI, P);[/COLOR] // ScrnBalance v6.10+
else {
// old style
if ( [color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color] >= 6 )
KFHumanPawn(P).CreateInventoryVeterancy("ScrnBruteGunnerPNW.BruteSA80LSW", GetInitialCostScaling(KFPRI, class'ScrnBruteGunnerPNW.BruteSA80LSWPickup'));
else if ( [color="yellow"]GetClientVeteranSkillLevel(KFPRI)[/color] == 5 )
KFHumanPawn(P).CreateInventoryVeterancy("ScrnBruteGunnerPNW.BruteAK47AssaultRifle", GetInitialCostScaling(KFPRI, class'ScrnBruteGunnerPNW.BruteAK47Pickup'));
}
}
static function string GetCustomLevelInfo( byte Level )
{
local string S;
local byte BonusLevel;
S = Default.CustomLevelInfo;
BonusLevel = GetBonusLevel(Level)-6;
ReplaceText(S,"%L",string(BonusLevel+6));
ReplaceText(S,"%s",GetPercentStr(0.50 + 0.05*BonusLevel));
ReplaceText(S,"%c",GetPercentStr(1.00 + 0.10*BonusLevel));
ReplaceText(S,"%d",GetPercentStr(0.7 + fmin(0.2, 0.05*BonusLevel)));
ReplaceText(S,"%w",string(10 + BonusLevel/2));
return S;
}
defaultproperties
{
[COLOR="lime"] DefaultDamageType=Class'ScrnBruteGunnerPNW.DamTypeHeavy'
DefaultDamageTypeNoBonus=Class'ScrnBruteGunnerPNW.DamTypeHeavyBase' // allows perk progression, but doesn't add damage bonuses[/COLOR]
SRLevelEffects(0)="*** BONUS LEVEL 0 (ScrN Ed. PnW v1.40)|5% more damage with Heavy Guns|10% less recoil with all guns|10% slower movement speed|10% discount on Heavy Guns"
SRLevelEffects(1)="*** BONUS LEVEL 1 (ScrN Ed. PnW v1.40)|10% more damage with Heavy Guns|2 extra weight slots|20% larger Heavy Gun clips|15% less recoil with all guns|10% slower movement speed|20% discount on Heavy Guns"
SRLevelEffects(2)="*** BONUS LEVEL 2 (ScrN Ed. PnW v1.40)|20% more damage with Heavy Guns|4 extra weight slots|40% larger Heavy Gun clips|20% less recoil with all guns|10% slower movement speed|30% discount on Heavy Guns"
SRLevelEffects(3)="*** BONUS LEVEL 3 (ScrN Ed. PnW v1.40)|30% more damage with Heavy Guns|6 extra weight slots|60% larger Heavy Gun clips|25% less recoil with all guns|10% slower movement speed|40% discount on Heavy Guns"
SRLevelEffects(4)="*** BONUS LEVEL 4 (ScrN Ed. PnW v1.40)|40% more damage with Heavy Guns|8 extra weight slots|80% larger Heavy Gun clips|30% less recoil with all guns|10% slower movement speed|50% discount on Heavy Guns"
SRLevelEffects(5)="*** BONUS LEVEL 5 (ScrN Ed. PnW v1.40)|50% more damage with Heavy Guns|10 extra weight slots|100% larger Heavy Gun clips|40% less recoil with all guns|10% slower movement speed|60% discount on Heavy Guns|Spawns With AK-47"
SRLevelEffects(6)="*** BONUS LEVEL 6 (ScrN Ed. PnW v1.40)|50% more damage with Heavy Guns|10 extra weight slots|100% larger Heavy Gun clips|50% less recoil with all guns|10% slower movement speed|70% discount on Heavy Guns|Spawns With SA-80"
CustomLevelInfo="*** BONUS LEVEL %L (ScrN Ed. PnW v1.40)|%s more damage with Heavy Guns|%w extra weight slots|%c larger Heavy Gun clips|50% less recoil with all guns|10% slower movement speed|%d discount on Heavy Guns"
PerkIndex=10
OnHUDIcon=Texture'BruteGunnerPerkIcons.Perks.BruteGunnerPerkRed'
OnHUDGoldIcon=Texture'BruteGunnerPerkIcons.Perks.BruteGunnerPerkGold'
[COLOR="Lime"] OnHUDIcons(0)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Brute',StarIcon=Texture'KillingFloorHUD.HUD.Hud_Perk_Star',DrawColor=(B=255,G=255,R=255,A=255))
OnHUDIcons(1)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Brute_Gold',StarIcon=Texture'KillingFloor2HUD.Perk_Icons.Hud_Perk_Star_Gold',DrawColor=(B=255,G=255,R=255,A=255))
OnHUDIcons(2)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Brute_Green',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Green',DrawColor=(B=255,G=255,R=255,A=255))
OnHUDIcons(3)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Brute_Blue',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Blue',DrawColor=(B=255,G=255,R=255,A=255))
OnHUDIcons(4)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Brute_Purple',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Purple',DrawColor=(B=255,G=255,R=255,A=255))
OnHUDIcons(5)=(PerkIcon=Texture'ScrnTex.Perks.Perk_Brute_Orange',StarIcon=Texture'ScrnTex.Perks.Hud_Perk_Star_Orange',DrawColor=(B=255,G=255,R=255,A=255)) [/COLOR]
Requirements(0)="Deal %x damage with Heavy Guns"
VeterancyName="Brute Gunner"
}
And default damage types:
Spoiler!
Code:
// Brute Gunner's base damage type
class DamTypeHeavyBase extends KFWeaponDamageType
abstract;
static function AwardDamage(KFSteamStatsAndAchievements KFStatsAndAchievements, int Amount)
{
if( SRStatsBase(KFStatsAndAchievements)!=None && SRStatsBase(KFStatsAndAchievements).Rep!=None )
SRStatsBase(KFStatsAndAchievements).Rep.ProgressCustomValue(Class'BruteGunnerPerkProg',Amount);
}
defaultproperties
{
bCheckForHeadShots=false
}
Spoiler!
Code:
class DamTypeHeavy extends DamTypeHeavyBase
abstract;
Last edited: