// Base perk. Class Ext_PerkBase extends Info NotPlaceable Abstract Config(ServerExt) DependsOn(ExtWebAdmin_UI); var array WebConfigs; var ExtPerkManager PerkManager; var Controller PlayerOwner; var() localized string PerkName; var() Texture2D PerkIcon; var() class BasePerk; // KF perk that this perk is based on. var() class PrimaryMelee,PrimaryWeapon; var() class PrimaryWeaponDef,SecondaryWeaponDef,KnifeWeaponDef,GrenadeWeaponDef; var() class GrenadeClass,PerkGrenade,SuperGrenade; var() int HealExpUpNum,WeldExpUpNum; // Efficiency of healing and welding XP up. // For trader. var() array > AutoBuyLoadOutPath; // Config init stuff. var config int ConfigVersion; var const int CurrentConfigVer; // Variables. var config int FirstLevelExp, // How much EXP needed for first level. LevelUpExpCost, // How much EXP needed for every level up. LevelUpIncCost, // How much EXP increase needed for each level up. MinimumLevel, MaximumLevel, StarPointsPerLevel, MinLevelForPrestige, // Minimum level required for perk prestige. PrestigeSPIncrease, // Starpoint increase per prestige levelup. MaxPrestige, MinimalDataLevel; // Maximum prestige level. var config float PrestigeXPReduce; // Amount of XP cost is reduced for each prestige. var config array TraitClasses; var array Modifiers; var int CurrentLevel, // Current level player is on. CurrentEXP, // Current amount of EXP user has. NextLevelEXP, // Experience needed for next level. CurrentSP, // Current amount of star points. LastLevelEXP, // Number of XP was needed for last level. CurrentPrestige; // Current prestige level. struct FPerkStat { var config int MaxValue,CostPerValue; var config float Progress; var config name StatType; var transient int CurrentValue,OldValue; var transient float DisplayValue; var transient string UIName; }; var config array PerkStats; struct FDefPerkStat { var int MaxValue,CostPerValue; var float Progress; var name StatType; var bool bHiddenConfig; // Hide this config by default. }; var() array DefPerkStats; var() array< class > DefTraitList; struct FPlayerTrait { var class TraitType; var byte CurrentLevel; var Ext_TraitDataStore Data; }; var array PerkTraits; // Server -> Client replication variables var byte RepState; var int RepIndex; var transient float NextAuthTime; var int ToxicDartDamage; var byte EnemyHealthRange; var() array EnemyDistDraw; var bool bOwnerNetClient,bClientAuthorized,bPerkNetReady,bHasNightVision,bCanBeGrabbed,bExplosiveWeld,bExplodeOnContact,bNapalmFire,bFireExplode,bToxicDart,bTacticalReload,bHeavyArmor,bHasSWATEnforcer; var localized string StatSpeed; var localized string StatDamage; var localized string StatRecoil; var localized string StatSpread; var localized string StatRate; var localized string StatReload; var localized string StatHealth; var localized string StatKnockDown; var localized string StatWelder; var localized string StatHeal; var localized string StatMag; var localized string StatSpare; var localized string StatOffDamage; var localized string StatSelfDamage; var localized string StatArmor; var localized string StatPoisonDmg; var localized string StatSonicDmg; var localized string StatFireDmg; var localized string StatAllDmg; var localized string StatHeadDamage; var localized string StatHealRecharge; reliable client simulated function string UIName(FDefPerkStat DefPerkStat) { switch (DefPerkStat.StatType) { case name("Speed"): return StatSpeed; case name("Damage"): return StatDamage; case name("Recoil"): return StatRecoil; case name("Spread"): return StatSpread; case name("Rate"): return StatRate; case name("Reload"): return StatReload; case name("Health"): return StatHealth; case name("KnockDown"): return StatKnockDown; case name("Welder"): return StatWelder; case name("Heal"): return StatHeal; case name("Mag"): return StatMag; case name("Spare"): return StatSpare; case name("OffDamage"): return StatOffDamage; case name("SelfDamage"): return StatSelfDamage; case name("Armor"): return StatArmor; case name("PoisonDmg"): return StatPoisonDmg; case name("SonicDmg"): return StatSonicDmg; case name("FireDmg"): return StatFireDmg; case name("AllDmg"): return StatAllDmg; case name("HeadDamage"): return StatHeadDamage; case name("HealRecharge"): return StatHealRecharge; } return ""; } replication { // Things the server should send to the client. if (true) CurrentLevel,CurrentPrestige,CurrentEXP,NextLevelEXP,CurrentSP,LastLevelEXP,bHasNightVision,MinLevelForPrestige,PrestigeSPIncrease,MaxPrestige,bTacticalReload,EnemyHealthRange; } simulated final function bool IsWeaponOnPerk(KFWeapon W) { if (class(BasePerk) != None) return true; //if (W.AllowedForAllPerks()) // return true; return W!=None && W.GetWeaponPerkClass(BasePerk)==BasePerk; } simulated static function string GetPerkIconPath(int Level) { return "img://"$PathName(default.PerkIcon); } simulated function PostBeginPlay() { local int i,j; local class T; if (WorldInfo.NetMode==NM_Client) { PerkStats.Length = 0; // Prevent client desync with client settings. PlayerOwner = GetALocalPlayerController(); SetTimer(0.01,false,'InitPerk'); } else { RemoteRole = ROLE_None; // Make sure these actors get replicated in order to client. PlayerOwner = Controller(Owner); if (PlayerOwner==None) { `Log(Self@"spawned without owner."); Destroy(); return; } bOwnerNetClient = (PlayerController(Owner)!=None && LocalPlayer(PlayerController(Owner).Player)==None); // Load trait classes. j = 0; for (i=0; i(DynamicLoadObject(TraitClasses[i],Class'Class')); if (T==None || !T.Static.IsEnabled(Self)) continue; PerkTraits.Length = j+1; PerkTraits[j].TraitType = T; ++j; } // Setup serverside stat info (for XML log files). for (j=0; j=0) PerkStats[j].UIName = UIName(DefPerkStats[i]); else { // Fallback to parent perk for trying to find name. i = Class'Ext_PerkBase'.Default.DefPerkStats.Find('StatType',PerkStats[j].StatType); if (i>=0) PerkStats[j].UIName = UIName(Class'Ext_PerkBase'.Default.DefPerkStats[i]); else PerkStats[j].UIName = string(PerkStats[j].StatType); // Fallback to stat name then... } } } } simulated function InitPerk() { if (PlayerOwner==None) PlayerOwner = GetALocalPlayerController(); if (PerkManager==None) { foreach DynamicActors(class'ExtPerkManager',PerkManager) { PerkManager.RegisterPerk(Self); break; } } } simulated function Destroyed() { local int i; if (PerkManager!=None) PerkManager.UnregisterPerk(Self); if (WorldInfo.NetMode!=NM_Client) { for (i=0; i0 ? (string(CurrentPrestige)$"-"$string(CurrentLevel)) : string(CurrentLevel)); } // For progress bar on HUD simulated final function float GetProgressPercent() { return FClamp(float(CurrentEXP-LastLevelEXP) / FMax(float(NextLevelEXP-LastLevelEXP),1.f),0.f,1.f); } // Whetever if user can use prestige now. simulated final function bool CanPrestige() { return (MinLevelForPrestige>=0 && CurrentPrestige=MinLevelForPrestige : false; } // Whetever to save this perk status or not final function bool HasAnyProgress() { return (CurrentEXP>0 || CurrentPrestige>0); } reliable client simulated function ClientReceiveStat(int Index, int MaxValue, int CostPerValue, name Type, int CurValue, float Progress) { local int i; if (WorldInfo.NetMode==NM_Client) { if (PerkStats.Length<=Index) PerkStats.Length = Index+1; PerkStats[Index].MaxValue = MaxValue; PerkStats[Index].CostPerValue = CostPerValue; PerkStats[Index].StatType = Type; PerkStats[Index].CurrentValue = CurValue; PerkStats[Index].DisplayValue = 0.f; PerkStats[Index].Progress = Progress; } i = DefPerkStats.Find('StatType',Type); if (i>=0) PerkStats[Index].UIName = UIName(DefPerkStats[i]); else { // Fallback to parent perk for trying to find name. i = Class'Ext_PerkBase'.Default.DefPerkStats.Find('StatType',Type); if (i>=0) PerkStats[Index].UIName = UIName(Class'Ext_PerkBase'.Default.DefPerkStats[i]); else PerkStats[Index].UIName = string(Type); // Fallback to stat name then... } } reliable client simulated function ClientSetStatValue(int Index, int NewValue) { if (PerkStats.Length<=Index) PerkStats.Length = Index+1; PerkStats[Index].CurrentValue = NewValue; if (bPerkNetReady) ApplyEffects(); } reliable client simulated function ClientReceiveTrait(int Index, class TC, byte Lvl) { if (PerkTraits.Length<=Index) PerkTraits.Length = Index+1; PerkTraits[Index].TraitType = TC; PerkTraits[Index].CurrentLevel = Lvl; } reliable client simulated function ClientReceiveTraitData(int Index, string Data) { if (WorldInfo.NetMode==NM_Client) PerkTraits[Index].TraitType.Static.ClientSetRepData(Data); } reliable client simulated function ClientReceiveTraitLvl(int Index, byte NewLevel) { PerkTraits[Index].CurrentLevel = NewLevel; } final function SetPerkStat(name Type, int Value) { local int i; i = PerkStats.Find('StatType',Type); if (i>=0) PerkStats[i].CurrentValue = Value; } final function int GetPerkStat(name Type) { local int i; i = PerkStats.Find('StatType',Type); if (i==-1) return 0; return PerkStats[i].CurrentValue; } function bool EarnedEXP(int EXP) { local int n; bForceNetUpdate = true; CurrentEXP+=EXP; while (CurrentEXP>=NextLevelEXP && CurrentLevel0) { CurrentSP+=(n*(StarPointsPerLevel+CurrentPrestige*PrestigeSPIncrease)); if (PerkManager.PRIOwner!=None && PerkManager.CurrentPerk==Self) UpdatePRILevel(); // TODO - broadcast level up messages. if (ExtPlayerController(PlayerOwner)!=None) ExtPlayerController(PlayerOwner).ReceiveLevelUp(Self,CurrentLevel); } return true; } final function UpdatePRILevel() { PerkManager.PRIOwner.SetLevelProgress(CurrentLevel,CurrentPrestige,MinimumLevel,MaximumLevel); } // XML output function OutputXML(ExtStatWriter Data) { local int i; Data.StartIntendent("perk","class",string(Class.Name)); Data.WriteValue("perkname",PerkName); Data.WriteValue("level",string(CurrentLevel)); Data.WriteValue("prestige",string(CurrentPrestige)); Data.WriteValue("exp",string(CurrentEXP)); Data.WriteValue("points",string(CurrentSP)); Data.WriteValue("exptilnext",string(NextLevelEXP)); Data.WriteValue("exponprev",string(LastLevelEXP)); for (i=0; i0) { Data.StartIntendent("stat","type",string(PerkStats[i].StatType)); Data.WriteValue("name",GetStatUIStr(i)); Data.WriteValue("value",string(PerkStats[i].CurrentValue)); Data.WriteValue("progress",string(PerkStats[i].DisplayValue)); Data.EndIntendent(); } } for (i=0; i0) { Data.StartIntendent("trait","class",string(PerkTraits[i].TraitType.Name)); Data.WriteValue("name",PerkTraits[i].TraitType.Default.TraitName); Data.WriteValue("level",string(PerkTraits[i].CurrentLevel)); Data.EndIntendent(); } } Data.EndIntendent(); } // Data saving. function SaveData(ExtSaveDataBase Data) { local int i,j; // Write current EXP. Data.SaveInt(CurrentEXP,3); // Write current prestige Data.SaveInt(CurrentPrestige,3); // Count number of given stats j = 0; for (i=0; i0) ++j; // Then perk stats. Data.SaveInt(j); for (i=0; i0) { Data.SaveStr(string(PerkStats[i].StatType)); Data.SaveInt(PerkStats[i].CurrentValue,1); } } // Count bought traits. j = 0; for (i=0; i0) ++j; // Then traits. Data.SaveInt(j); for (i=0; i0) { Data.SaveStr(string(PerkTraits[i].TraitType)); Data.SaveInt(PerkTraits[i].CurrentLevel); } } } // Data loading. function LoadData(ExtSaveDataBase Data) { local int i,j,l,n; local string S; CurrentEXP = Data.ReadInt(3); // if (MinimalDataLevel > 0) // { // i = GetNeededExp(MinimalDataLevel-1) // if (i > CurrentEXP) // CurrentEXP = i // } if (Data.GetArVer()>=1) CurrentPrestige = Data.ReadInt(3); l = Data.ReadInt(); // Perk stats length. for (i=0; i=GetNeededExp(a)) ++a; break; } i = a+((b-a)>>1); if (InExp 0) { i = GetNeededExp(MinimalDataLevel-1); if (i > CurrentEXP) CurrentEXP = i; } // Set to initial level player is on after configures has loaded. CurrentLevel = CalcLevelForExp(CurrentEXP); CurrentSP = CurrentLevel*(StarPointsPerLevel+CurrentPrestige*PrestigeSPIncrease); NextLevelEXP = GetNeededExp(CurrentLevel); LastLevelEXP = (CurrentLevel>MinimumLevel ? GetNeededExp(CurrentLevel-1) : 0); // Now verify the points player used on individual stats. for (i=0; i0) { PerkStats[i].CurrentValue = Min(PerkStats[i].CurrentValue,PerkStats[i].MaxValue); a = PerkStats[i].CurrentValue*PerkStats[i].CostPerValue; if (CurrentSP>a) CurrentSP-=a; else if (CurrentSP<=0) // No points at all for this. PerkStats[i].CurrentValue = 0; else // Nope, reduce the stat! { a = CurrentSP/PerkStats[i].CostPerValue; PerkStats[i].CurrentValue = a; CurrentSP-=(a*PerkStats[i].CostPerValue); } } } // Then verify trait levels and costs. MT = 0; for (i=0; i0) { PerkTraits[i].CurrentLevel = Min(PerkTraits[i].CurrentLevel,PerkTraits[i].TraitType.Default.NumLevels); if (PerkTraits[i].TraitType.Default.LoadPriority>0) MT = Max(MT,PerkTraits[i].TraitType.Default.LoadPriority); else { if (!PerkTraits[i].TraitType.Static.MeetsRequirements(PerkTraits[i].CurrentLevel-1,Self)) a = 0; else { for (a=0; aCurrentSP) break; CurrentSP-=b; } } PerkTraits[i].CurrentLevel = a; if (PerkTraits[i].CurrentLevel>0 && PerkTraits[i].Data==None) PerkTraits[i].Data = PerkTraits[i].TraitType.Static.Initializefor (Self,ExtPlayerController(Owner)); } } if (PerkTraits[i].CurrentLevel==0 && PerkTraits[i].Data!=None) PerkTraits[i].TraitType.Static.CleanupTrait(ExtPlayerController(Owner),Self,PerkTraits[i].Data); } // Delayed loads. for (j=1; j<=MT; ++j) { for (i=0; i0 && PerkTraits[i].TraitType.Default.LoadPriority==j) { if (!PerkTraits[i].TraitType.Static.MeetsRequirements(PerkTraits[i].CurrentLevel-1,Self)) a = 0; else { for (a=0; aCurrentSP) break; CurrentSP-=b; } } PerkTraits[i].CurrentLevel = a; if (PerkTraits[i].CurrentLevel>0 && PerkTraits[i].Data==None) PerkTraits[i].Data = PerkTraits[i].TraitType.Static.Initializefor (Self,ExtPlayerController(Owner)); } if (PerkTraits[i].CurrentLevel==0 && PerkTraits[i].Data!=None) PerkTraits[i].TraitType.Static.CleanupTrait(ExtPlayerController(Owner),Self,PerkTraits[i].Data); } } ApplyEffects(); if (PerkManager.CurrentPerk==Self) ActivateTraits(); } // Check the needed amount of EXP for a perk. function int GetNeededExp(int LevelNum) { if (LevelNum=MaximumLevel) return 0; LevelNum-=MinimumLevel; LevelNum = (FirstLevelExp+(LevelNum*LevelUpExpCost)+(LevelNum*LevelNum*LevelUpIncCost)); if (CurrentPrestige>0 && PrestigeXPReduce>0) LevelNum *= (1.f / (1.f + PrestigeXPReduce*CurrentPrestige)); return LevelNum; } // Configure initialization. static function CheckConfig() { local int i; local class T; if (Default.ConfigVersion!=Default.CurrentConfigVer) { UpdateConfigs(Default.ConfigVersion); Default.ConfigVersion = Default.CurrentConfigVer; StaticSaveConfig(); } for (i=0; i(DynamicLoadObject(Default.TraitClasses[i],Class'Class')); if (T!=None) T.Static.CheckConfig(); } } static function UpdateConfigs(int OldVer) { local int i,j; if (OldVer==0) { Default.FirstLevelExp = 400; Default.LevelUpExpCost = 500; Default.LevelUpIncCost = 65; Default.MinimumLevel = 0; Default.MaximumLevel = 150; Default.StarPointsPerLevel = 15; // Prestige. Default.MinLevelForPrestige = 140; Default.PrestigeSPIncrease = 1; Default.MaxPrestige = 20; Default.MinimalDataLevel = 0; Default.PrestigeXPReduce = 0.05; Default.PerkStats.Length = 0; AddStatsCfg(0); Default.TraitClasses.Length = Default.DefTraitList.Length; for (i=0; i=0) Default.PerkStats[i].Progress = Default.DefPerkStats[j].Progress; } // Add off-perk damage stat. AddStatsCfg(12); } else if (OldVer<=3) AddStatsCfg(13); // Add self damage. else if (OldVer<=4) AddStatsCfg(15); // Add poison damage. else if (OldVer<=7) AddStatsCfg(16); // Add sonic/fire damage. else if (OldVer<=12) AddStatsCfg(18); // Add all damage. else if (OldVer<=13) AddStatsCfg(19); // Add HeadDamage and HealRecharge if (OldVer<=5) { // Add prestige Default.MinLevelForPrestige = 140; Default.PrestigeSPIncrease = 1; Default.MaxPrestige = 20; Default.PrestigeXPReduce = 0.05; } Default.TraitClasses.Length = Default.DefTraitList.Length; for (i=0; i=0) // Don't add if already found for some reason. continue; Default.PerkStats.Length = j+1; Default.PerkStats[j].MaxValue = Default.DefPerkStats[i].MaxValue; Default.PerkStats[j].CostPerValue = Default.DefPerkStats[i].CostPerValue; Default.PerkStats[j].StatType = Default.DefPerkStats[i].StatType; Default.PerkStats[j].Progress = Default.DefPerkStats[i].Progress; ++j; } } // WebAdmin UI stuff. static function InitWebAdmin(ExtWebAdmin_UI UI) { local class T; local int i; UI.AddSettingsPage("Perk "$Default.PerkName,Default.Class,Default.WebConfigs,GetValue,ApplyValue); for (i=0; i(DynamicLoadObject(Default.TraitClasses[i],Class'Class')); if (T==None || UI.HasConfigfor (T)) continue; T.Static.InitWebAdmin(UI); } } static function string GetValue(name PropName, int ElementIndex) { switch (PropName) { case 'FirstLevelExp': return string(Default.FirstLevelExp); case 'LevelUpExpCost': return string(Default.LevelUpExpCost); case 'LevelUpIncCost': return string(Default.LevelUpIncCost); case 'MinimumLevel': return string(Default.MinimumLevel); case 'MaximumLevel': return string(Default.MaximumLevel); case 'StarPointsPerLevel': return string(Default.StarPointsPerLevel); case 'TraitClasses': return ElementIndex==-1 ? string(Default.TraitClasses.Length) : Default.TraitClasses[ElementIndex]; case 'PerkStats': return ElementIndex==-1 ? string(Default.PerkStats.Length) : Default.PerkStats[ElementIndex].StatType$","$Default.PerkStats[ElementIndex].MaxValue$","$Default.PerkStats[ElementIndex].CostPerValue$","$Default.PerkStats[ElementIndex].Progress; case 'MinLevelForPrestige': return string(Default.MinLevelForPrestige); case 'PrestigeSPIncrease': return string(Default.PrestigeSPIncrease); case 'MinimalDataLevel': return string(Default.MinimalDataLevel); case 'MaxPrestige': return string(Default.MaxPrestige); case 'PrestigeXPReduce': return string(Default.PrestigeXPReduce); } } static function ApplyValue(name PropName, int ElementIndex, string Value) { switch (PropName) { case 'FirstLevelExp': Default.FirstLevelExp = int(Value); break; case 'LevelUpExpCost': Default.LevelUpExpCost = int(Value); break; case 'MinimalDataLevel': Default.MinimalDataLevel = int(Value); break; case 'LevelUpIncCost': Default.LevelUpIncCost = int(Value); break; case 'MinimumLevel': Default.MinimumLevel = int(Value); break; case 'MaximumLevel': Default.MaximumLevel = int(Value); break; case 'StarPointsPerLevel': Default.StarPointsPerLevel = int(Value); break; case 'TraitClasses': if (Value=="#DELETE") Default.TraitClasses.Remove(ElementIndex,1); else { if (ElementIndex>=Default.TraitClasses.Length) Default.TraitClasses.Length = ElementIndex+1; Default.TraitClasses[ElementIndex] = Value; } break; case 'PerkStats': if (Value=="#DELETE") Default.PerkStats.Remove(ElementIndex,1); else { if (ElementIndex>=Default.PerkStats.Length) Default.PerkStats.Length = ElementIndex+1; Default.PerkStats[ElementIndex] = ParsePerkStatStr(Value); } break; case 'MinLevelForPrestige': Default.MinLevelForPrestige = int(Value); break; case 'PrestigeSPIncrease': Default.PrestigeSPIncrease = int(Value); break; case 'MaxPrestige': Default.MaxPrestige = int(Value); break; case 'PrestigeXPReduce': Default.PrestigeXPReduce = float(Value); break; default: return; } StaticSaveConfig(); } static final function FPerkStat ParsePerkStatStr(string S) { local FPerkStat Res; local int i; i = InStr(S,","); if (i==-1) return Res; Res.StatType = name(Left(S,i)); S = Mid(S,i+1); i = InStr(S,","); if (i==-1) return Res; Res.MaxValue = int(Left(S,i)); S = Mid(S,i+1); i = InStr(S,","); if (i==-1) return Res; Res.CostPerValue = int(Left(S,i)); Res.Progress = float(Mid(S,i+1)); return Res; } // Amount and iStat values are verified already by ServerExtMut. function bool IncrementStat(int iStat, int Amount) { PerkStats[iStat].CurrentValue+=Amount; if (bOwnerNetClient) ClientSetStatValue(iStat,PerkStats[iStat].CurrentValue); PerkManager.bStatsDirty = true; ApplyEffects(); bForceNetUpdate = true; return true; } simulated function ApplyEffects() { local int i; for (i=0; i0) { if (PerkTraits[i].TraitType.Default.bPostApplyEffect) bSec = true; else PerkTraits[i].TraitType.Static.ApplyEffectOn(P,Self,PerkTraits[i].CurrentLevel,PerkTraits[i].Data); } } if (bSec) { for (i=0; i0 && PerkTraits[i].TraitType.Default.bPostApplyEffect) PerkTraits[i].TraitType.Static.ApplyEffectOn(P,Self,PerkTraits[i].CurrentLevel,PerkTraits[i].Data); } } } // Player joined/perk changed. function ActivateTraits() { local int i; local KFPawn_Human KFP; local bool bSec; KFP = KFPawn_Human(PlayerOwner.Pawn); if (KFP!=None && !KFP.IsAliveAndWell()) KFP = None; for (i=0; i0) { PerkTraits[i].TraitType.Static.TraitActivate(Self,PerkTraits[i].CurrentLevel,PerkTraits[i].Data); if (KFP!=None) { if (PerkTraits[i].TraitType.Default.bPostApplyEffect) bSec = true; else PerkTraits[i].TraitType.Static.ApplyEffectOn(KFP,Self,PerkTraits[i].CurrentLevel,PerkTraits[i].Data); } } } if (bSec) { for (i=0; i0 && PerkTraits[i].TraitType.Default.bPostApplyEffect) PerkTraits[i].TraitType.Static.ApplyEffectOn(KFP,Self,PerkTraits[i].CurrentLevel,PerkTraits[i].Data); } } } // Player disconnected/perk changed. function DeactivateTraits() { local int i; for (i=0; i0) PerkTraits[i].TraitType.Static.TraitDeActivate(Self,PerkTraits[i].CurrentLevel,PerkTraits[i].Data); } } simulated unreliable client function ClientAuth() { if (Owner==None) SetOwner(PlayerOwner); ServerAck(); } unreliable server function ServerAck() { if (!bClientAuthorized) { bClientAuthorized = true; RepState = 0; RepIndex = 0; SetTimer(0.01+FRand()*0.025,true,'ReplicateTimer'); } } function ReplicateTimer() { switch (RepState) { case 0: // Send all perk stats if (RepIndex>=PerkStats.Length) { ++RepState; RepIndex = 0; } else { ClientReceiveStat(RepIndex,PerkStats[RepIndex].MaxValue,PerkStats[RepIndex].CostPerValue,PerkStats[RepIndex].StatType,PerkStats[RepIndex].CurrentValue,PerkStats[RepIndex].Progress); ++RepIndex; } break; case 1: // Send all traits if (RepIndex>=PerkTraits.Length) ++RepState; else { ClientReceiveTrait(RepIndex,PerkTraits[RepIndex].TraitType,PerkTraits[RepIndex].CurrentLevel); ClientReceiveTraitData(RepIndex,PerkTraits[RepIndex].TraitType.Static.GetRepData()); ++RepIndex; } break; default: ClearTimer('ReplicateTimer'); bPerkNetReady = true; ClientIsReady(); // Notify client were ready. } } simulated reliable client function ClientIsReady() { bPerkNetReady = true; ApplyEffects(); } simulated function string GetStatUIStr(int iStat) { local string S; local bool bLoop; S = string(Abs(PerkStats[iStat].DisplayValue*100.f)); bLoop = true; // Chop off float digits that aren't needed. while (bLoop) { switch (Right(S,1)) { case "0": S = Left(S,Len(S)-1); break; case ".": S = Left(S,Len(S)-1); bLoop = false; break; default: bLoop = false; } } return Repl(PerkStats[iStat].UIName,"&",S); } final function UnloadStats(optional byte Mode) { local int i,j; local KFPawn_Human KFP; PerkManager.bStatsDirty = true; if (Mode<=1) { // Reset stats. for (i=0; i0) { CurrentSP+=(PerkStats[i].CurrentValue*PerkStats[i].CostPerValue); PerkStats[i].CurrentValue = 0; if (bOwnerNetClient) ClientSetStatValue(i,0); } } ApplyEffects(); } if (Mode==0 || Mode==2) { KFP = KFPawn_Human(PlayerOwner.Pawn); if (KFP!=None && !KFP.IsAliveAndWell()) KFP = None; // Reset traits. for (i=0; i0) { for (j=0; j DamType) { local int i; // Doing 2 passes of this so that things don't go out of order (spawn retaliation effect when you get redeemed etc) for (i=0; i0 && PerkTraits[i].TraitType.Default.bHighPriorityDeath && PerkTraits[i].TraitType.Static.PreventDeath(Player,Killer,DamType,Self,PerkTraits[i].CurrentLevel,PerkTraits[i].Data)) return true; } for (i=0; i0 && !PerkTraits[i].TraitType.Default.bHighPriorityDeath && PerkTraits[i].TraitType.Static.PreventDeath(Player,Killer,DamType,Self,PerkTraits[i].CurrentLevel,PerkTraits[i].Data)) return true; } return false; } simulated function PlayerDied() { local int i; if (WorldInfo.NetMode!=NM_Client) { for (i=0; i0) PerkTraits[i].TraitType.Static.PlayerDied(Self,PerkTraits[i].CurrentLevel,PerkTraits[i].Data); } } } // Stat modifier functions. simulated function float ApplyEffect(name Type, float Value, float Progress) { local bool bActivePerk; bActivePerk = (PerkManager!=None && PerkManager.CurrentPerk==Self); switch (Type) { case 'Speed': Modifiers[0] = 1.f + (Value*Progress); break; case 'Damage': Modifiers[1] = 1.f + (Value*Progress); break; case 'Recoil': Modifiers[2] = 1.f / (1.f+Value*Progress); break; case 'Spread': Modifiers[3] = 1.f / (1.f+Value*Progress); break; case 'Rate': Modifiers[4] = 1.f / (1.f+Value*Progress); break; case 'Reload': Modifiers[5] = 1.f / (1.f+Value*Progress); break; case 'Health': Modifiers[6] = 1.f + (Value*Progress); if (bActivePerk && PlayerOwner.Pawn!=None) { PlayerOwner.Pawn.HealthMax = PlayerOwner.Pawn.Default.Health; ModifyHealth(PlayerOwner.Pawn.HealthMax); } break; case 'KnockDown': Modifiers[7] = FMin(1.f + (Value*Progress),2.f); return (Modifiers[7]-1.f); case 'Welder': Modifiers[8] = 1.f + (Value*Progress); break; case 'Heal': Modifiers[9] = 1.f + (Value*Progress); break; case 'Mag': Modifiers[10] = 1.f + (Value*Progress); if (bActivePerk && WorldInfo.NetMode!=NM_Client && PlayerOwner.Pawn!=None && PlayerOwner.Pawn.InvManager!=None) UpdateAmmoStatus(PlayerOwner.Pawn.InvManager); break; case 'Spare': Modifiers[11] = 1.f + (Value*Progress); if (bActivePerk && WorldInfo.NetMode!=NM_Client && PlayerOwner.Pawn!=None && PlayerOwner.Pawn.InvManager!=None) UpdateAmmoStatus(PlayerOwner.Pawn.InvManager); break; case 'OffDamage': Modifiers[12] = 1.f + (Value*Progress); break; case 'SelfDamage': Modifiers[13] = 1.f / (1.f+Value*Progress); break; case 'Armor': Modifiers[14] = (Value*Progress*100.f); if (bActivePerk && KFPawn_Human(PlayerOwner.Pawn)!=None) { KFPawn_Human(PlayerOwner.Pawn).MaxArmor = KFPawn_Human(PlayerOwner.Pawn).Default.MaxArmor; ModifyArmor(KFPawn_Human(PlayerOwner.Pawn).MaxArmor); } return FMin(Value*Progress,1.55); case 'PoisonDmg': Modifiers[15] = 1.f / (1.f+Value*Progress); break; case 'SonicDmg': Modifiers[16] = 1.f / (1.f+Value*Progress); break; case 'FireDmg': Modifiers[17] = 1.f / (1.f+Value*Progress); break; case 'AllDmg': Modifiers[18] = 1.f / (1.f+Value*Progress); break; case 'HeadDamage': Modifiers[19] = Value*Progress; break; case 'HealRecharge': Modifiers[20] = 1.f / (1.f+Value*Progress); break; } return (Value*Progress); } simulated function ModifyDamageGiven(out int InDamage, optional Actor DamageCauser, optional KFPawn_Monster MyKFPM, optional KFPlayerController DamageInstigator, optional class DamageType, optional int HitZoneIdx) { if (BasePerk==None || (DamageType!=None && DamageType.Default.ModifierPerkList.Find(BasePerk)>=0) || (KFWeapon(DamageCauser)!=None && IsWeaponOnPerk(KFWeapon(DamageCauser)))) { if (HitZoneIdx == 0) InDamage *= (Modifiers[1] + Modifiers[19]); else InDamage *= Modifiers[1]; } else if (DamageType==None || DamageType.Name!='KFDT_SuicideExplosive') InDamage *= Modifiers[12]; } simulated function ModifyDamageTaken(out int InDamage, optional class DamageType, optional Controller InstigatedBy) { if (InDamage>0) { if ((InstigatedBy==None || InstigatedBy==PlayerOwner) && class(DamageType)!=None) InDamage *= Modifiers[13]; else if (Modifiers[15]<1 && class(DamageType)!=None) InDamage = Max(InDamage*Modifiers[15],1); // Do at least 1 damage. else if (Modifiers[16]<1 && class(DamageType)!=None) InDamage = Max(InDamage*Modifiers[16],1); else if (Modifiers[17]<1 && class(DamageType)!=None) InDamage = Max(InDamage*Modifiers[17],1); if (Modifiers[18]<1 && InstigatedBy!=None && InstigatedBy!=PlayerOwner) InDamage = Max(InDamage*Modifiers[18],1); } } simulated function ModifyRecoil(out float CurrentRecoilModifier, KFWeapon KFW) { if (IsWeaponOnPerk(KFW)) CurrentRecoilModifier *= Modifiers[2]; } simulated function ModifySpread(out float InSpread) { InSpread *= Modifiers[3]; } simulated function ModifyRateOfFire(out float InRate, KFWeapon KFW) { if (IsWeaponOnPerk(KFW)) InRate *= Modifiers[4]; } simulated function float GetReloadRateScale(KFWeapon KFW) { return (IsWeaponOnPerk(KFW) ? Modifiers[5] : 1.f); } function ModifyHealth(out int InHealth) { InHealth *= Modifiers[6]; } function ModifyArmor(out byte MaxArmor) { MaxArmor = Min(MaxArmor+Modifiers[14],255); } function float GetKnockdownPowerModifier() { return Modifiers[7]; } function float GetStunPowerModifier(optional class DamageType, optional byte HitZoneIdx) { return Modifiers[7]; } function float GetStumblePowerModifier( optional KFPawn KFP, optional class DamageType, optional out float CooldownModifier, optional byte BodyPart ) { return Modifiers[7]; } simulated function ModifyMeleeAttackSpeed(out float InDuration); function AddDefaultInventory(KFPawn P) { local int i; if (PrimaryWeapon!=None) P.DefaultInventory.AddItem(PrimaryWeapon); P.DefaultInventory.AddItem(PrimaryMelee); if (KFInventoryManager(P.InvManager)!=None) KFInventoryManager(P.InvManager).MaxCarryBlocks = KFInventoryManager(P.InvManager).Default.MaxCarryBlocks+Modifiers[10]; for (i=0; i0) PerkTraits[i].TraitType.Static.AddDefaultInventory(P,Self,PerkTraits[i].CurrentLevel,PerkTraits[i].Data); } } simulated function ModifyWeldingRate(out float FastenRate, out float UnfastenRate) { FastenRate *= Modifiers[8]; UnfastenRate *= Modifiers[8]; } function bool RepairArmor(Pawn HealTarget) { return false; } function bool ModifyHealAmount(out float HealAmount) { HealAmount*=Modifiers[9]; return false; } simulated function ModifyMagSizeAndNumber(KFWeapon KFW, out int MagazineCapacity, optional array< Class > WeaponPerkClass, optional bool bSecondary=false, optional name WeaponClassname) { if (MagazineCapacity>2 && (KFW==None ? WeaponPerkClass.Find(BasePerk)>=0 : IsWeaponOnPerk(KFW))) // Skip boomstick for this. MagazineCapacity = Min(MagazineCapacity*Modifiers[10],255); } simulated function ModifySpareAmmoAmount(KFWeapon KFW, out int PrimarySpareAmmo, optional const out STraderItem TraderItem, optional bool bSecondary) { if (KFW==None ? TraderItem.AssociatedPerkClasses.Find(BasePerk)>=0 : IsWeaponOnPerk(KFW)) PrimarySpareAmmo*=Modifiers[11]; } simulated function bool ShouldMagSizeModifySpareAmmo(KFWeapon KFW, optional Class WeaponPerkClass) { return (KFW==None ? WeaponPerkClass==BasePerk : IsWeaponOnPerk(KFW)); } final function UpdateAmmoStatus(InventoryManager Inv) { local KFWeapon W; foreach Inv.InventoryActors(class'KFWeapon',W) { // if (IsWeaponOnPerk(W)) W.ReInitializeAmmoCounts(PerkManager); } } simulated function ModifyHealerRechargeTime(out float RechargeRate) { RechargeRate *= Modifiers[20]; } simulated function DrawSpecialPerkHUD(Canvas C) { if (EnemyHealthRange>0 && PlayerOwner!=None && KFPawn_Human(PlayerOwner.Pawn)!=None) DrawEnemyHealth(C); } simulated final function DrawEnemyHealth(Canvas C) { local KFPawn_Monster KFPM; local vector X,CameraLocation; X = vector(PlayerOwner.Pawn.GetViewRotation()); CameraLocation = PlayerOwner.Pawn.GetPawnViewLocation(); foreach WorldInfo.AllPawns(class'KFPawn_Monster',KFPM,CameraLocation,EnemyDistDraw[EnemyHealthRange-1]) { if (KFPM.IsAliveAndWell() && `TimeSince(KFPM.Mesh.LastRenderTime)<0.1f && KFPM.CanShowHealth() && KFPM.GetTeamNum()!=0 && (X Dot (KFPM.Location - CameraLocation))>0) DrawZedHealthbar(C,KFPM,CameraLocation); } } simulated final function DrawZedHealthbar(Canvas C, KFPawn_Monster KFPM, vector CameraLocation) { local vector ScreenPos, TargetLocation; local float HealthBarLength, HealthbarHeight, HealthScale; HealthbarLength = FMin(50.f * (float(C.SizeX) / 1024.f), 50.f) * class'KFGFxHudWrapper'.Default.FriendlyHudScale; HealthbarHeight = FMin(6.f * (float(C.SizeX) / 1024.f), 6.f) * class'KFGFxHudWrapper'.Default.FriendlyHudScale; HealthScale = FClamp(float(KFPM.Health) / float(KFPM.HealthMax),0.f,1.f); if (KFPM.bCrawler && KFPM.Floor.Z <= -0.7f && KFPM.Physics == PHYS_Spider) { TargetLocation = KFPM.Location + vect(0,0,-1) * KFPM.GetCollisionHeight() * 1.2; } else { TargetLocation = KFPM.Location + vect(0,0,1) * KFPM.GetCollisionHeight() * 1.2; } ScreenPos = C.Project(TargetLocation); if (ScreenPos.X < 0 || ScreenPos.X > C.SizeX || ScreenPos.Y < 0 || ScreenPos.Y > C.SizeY || !FastTrace(TargetLocation, CameraLocation)) return; C.EnableStencilTest(true); C.SetDrawColor(0, 0, 0, 255); C.SetPos(ScreenPos.X - HealthBarLength * 0.5, ScreenPos.Y); C.DrawTileStretched(class'KFPerk_Commando'.Default.WhiteMaterial, HealthbarLength, HealthbarHeight, 0, 0, 32, 32); C.SetDrawColor(237, 8, 0, 255); C.SetPos(ScreenPos.X - HealthBarLength * 0.5 + 1.0, ScreenPos.Y + 1.0); C.DrawTileStretched(class'KFPerk_Commando'.Default.WhiteMaterial, (HealthBarLength - 2.0) * HealthScale, HealthbarHeight - 2.0, 0, 0, 32, 32); C.EnableStencilTest(false); } function PlayerKilled(KFPawn_Monster Victim, class DamageType); function ModifyBloatBileDoT(out float DoTScaler) { DoTScaler = Modifiers[15]; } simulated function bool GetIsUberAmmoActive(KFWeapon KFW) { return false; } function UpdatePerkHeadShots(ImpactInfo Impact, class DamageType, int NumHit); function CheckForAirborneAgent(KFPawn HealTarget, class DamType, int HealAmount); simulated function float GetZedTimeModifier(KFWeapon W) { return 0.f; } simulated function bool GetUsingTactialReload(KFWeapon KFW) { return (bTacticalReload && IsWeaponOnPerk(KFW)); } simulated function float GetIronSightSpeedModifier(KFWeapon KFW) { return 1.f; } function OnWaveEnded(); function NotifyZedTimeStarted(); simulated function float GetZedTimeExtensions(byte Level) { return 1.f; } defaultproperties { CurrentConfigVer=14 bOnlyRelevantToOwner=true bCanBeGrabbed=true NetUpdateFrequency=1 GrenadeClass=class'KFProj_FragGrenade' PerkGrenade=class'KFProj_FragGrenade' SuperGrenade=class'ExtProj_SUPERGrenade' HealExpUpNum=12 WeldExpUpNum=180 ToxicDartDamage=15 NetPriority=4 SecondaryWeaponDef=class'KFWeapDef_9mm' KnifeWeaponDef=class'KFWeapDef_Knife_Commando' GrenadeWeaponDef=class'KFWeapDef_Grenade_Support' DefTraitList.Add(class'Ext_TraitGrenadeUpg') DefTraitList.Add(class'Ext_TraitNightvision') DefTraitList.Add(class'Ext_TraitAmmoReg') DefTraitList.Add(class'Ext_TraitHealthReg') DefTraitList.Add(class'Ext_TraitArmorReg') DefTraitList.Add(class'Ext_TraitCarryCap') DefTraitList.Add(class'Ext_TraitGrenadeCap') DefTraitList.Add(class'Ext_TraitMedicPistol') DefTraitList.Add(class'Ext_TraitZED_Summon') DefTraitList.Add(class'Ext_TraitZED_Health') DefTraitList.Add(class'Ext_TraitZED_Damage') DefTraitList.Add(class'Ext_TraitZED_SummonExt') DefTraitList.Add(class'Ext_TraitGhost') DefTraitList.Add(class'Ext_TraitRetali') DefTraitList.Add(class'Ext_TraitDuracell') DefTraitList.Add(class'Ext_TraitRagdoll') DefTraitList.Add(class'Ext_TraitAutoFire') DefTraitList.Add(class'Ext_TraitBunnyHop') DefTraitList.Add(class'Ext_TraitKnockback') WebConfigs.Add((PropType=0,PropName="FirstLevelExp",UIName="First Level XP",UIDesc="EXP required for the FIRST level")) WebConfigs.Add((PropType=0,PropName="LevelUpExpCost",UIName="Level Up XP",UIDesc="EXP cost for every next level (Level * ThisValue")) WebConfigs.Add((PropType=0,PropName="LevelUpIncCost",UIName="Level Up Inc XP",UIDesc="Increased EXP cost for every level ((Level^2) * ThisValue)")) WebConfigs.Add((PropType=0,PropName="MinimumLevel",UIName="Minimum Level",UIDesc="The minimum level of players")) WebConfigs.Add((PropType=0,PropName="MaximumLevel",UIName="Maximum Level",UIDesc="The maximum level of players")) WebConfigs.Add((PropType=0,PropName="StarPointsPerLevel",UIName="Star Points Per Lvl",UIDesc="Number of star points players earn per level")) WebConfigs.Add((PropType=2,PropName="TraitClasses",UIName="Trait Classes",UIDesc="The class names of traits players can buy",NumElements=-1)) WebConfigs.Add((PropType=2,PropName="PerkStats",UIName="Perk Stats",UIDesc="List of perk stats (format in: StatName,Max Stat,Cost Per Stat,Progress Per Level)",NumElements=-1)) WebConfigs.Add((PropType=0,PropName="MinLevelForPrestige",UIName="Min Level For Prestige",UIDesc="Minimum level required to prestige the perk (-1 = disabled)")) WebConfigs.Add((PropType=0,PropName="PrestigeSPIncrease",UIName="Prestige SP Increase",UIDesc="Star points increase per level for every prestige")) WebConfigs.Add((PropType=0,PropName="MaxPrestige",UIName="Max Prestige",UIDesc="Maximum prestige level")) WebConfigs.Add((PropType=0,PropName="PrestigeXPReduce",UIName="Prestige XP Reduce",UIDesc="Percent amount of XP cost is reduced for each prestige (1.0 = 1/2, or 50 % of XP)")) // WebConfigs.Add((PropType=0,PropName="MinimalDataLevel",UIName="Minimal Real Level",UIDesc="Minimal level for new players or who loads from saves")) DefPerkStats(0)=(MaxValue=50,CostPerValue=1,StatType="Speed",Progress=0.4) DefPerkStats(1)=(MaxValue=1000,CostPerValue=1,StatType="Damage",Progress=0.5) DefPerkStats(2)=(MaxValue=90,CostPerValue=1,StatType="Recoil",Progress=1) DefPerkStats(3)=(MaxValue=80,CostPerValue=1,StatType="Spread",Progress=0.75) DefPerkStats(4)=(MaxValue=1000,CostPerValue=1,StatType="Rate",Progress=0.5) DefPerkStats(5)=(MaxValue=1000,CostPerValue=1,StatType="Reload",Progress=0.5) DefPerkStats(6)=(MaxValue=150,CostPerValue=1,StatType="Health",Progress=1) DefPerkStats(7)=(MaxValue=100,CostPerValue=1,StatType="KnockDown",Progress=1) DefPerkStats(8)=(MaxValue=200,CostPerValue=1,StatType="Welder",bHiddenConfig=true,Progress=0.5) DefPerkStats(9)=(MaxValue=400,CostPerValue=1,StatType="Heal",bHiddenConfig=true,Progress=0.5) DefPerkStats(10)=(MaxValue=400,CostPerValue=1,StatType="Mag",Progress=1) DefPerkStats(11)=(MaxValue=500,CostPerValue=1,StatType="Spare",Progress=1) DefPerkStats(12)=(MaxValue=1000,CostPerValue=1,StatType="OffDamage",Progress=0.25) DefPerkStats(13)=(MaxValue=1000,CostPerValue=1,StatType="SelfDamage",Progress=1,bHiddenConfig=true) DefPerkStats(14)=(MaxValue=150,CostPerValue=1,StatType="Armor",Progress=1) DefPerkStats(15)=(MaxValue=1000,CostPerValue=1,StatType="PoisonDmg",Progress=1.5,bHiddenConfig=true) DefPerkStats(16)=(MaxValue=1000,CostPerValue=1,StatType="SonicDmg",Progress=1.5,bHiddenConfig=true) DefPerkStats(17)=(MaxValue=1000,CostPerValue=1,StatType="FireDmg",Progress=1.5,bHiddenConfig=true) DefPerkStats(18)=(MaxValue=500,CostPerValue=1,StatType="AllDmg",Progress=0.25) DefPerkStats(19)=(MaxValue=500,CostPerValue=1,StatType="HeadDamage",Progress=1,bHiddenConfig=true) DefPerkStats(20)=(MaxValue=200,CostPerValue=1,StatType="HealRecharge",Progress=0.5,bHiddenConfig=true) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(0.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(1.f) Modifiers.Add(0.f) Modifiers.Add(1.f) EnemyDistDraw.Add(500) EnemyDistDraw.Add(700) EnemyDistDraw.Add(1000) EnemyDistDraw.Add(1600) }