/* Extended Perk Manager Written by Marco */ Class ExtPerkManager extends KFPerk; enum EReplicateState { REP_CustomCharacters, REP_CustomInventory, REP_PerkClasses, REP_Done }; const CUR_SaveVersion=2; var int UserDataVersion; var transient float LastNapalmTime; // Server -> Client rep status var byte RepState; var int RepIndex; var array UserPerks; var Ext_PerkBase CurrentPerk; var int ExpUpStatus[2]; var string StrPerkName; var ExtPlayerReplicationInfo PRIOwner; var Controller PlayerOwner; // Stats var int TotalEXP,TotalKills,TotalPlayTime; var bool bStatsDirty,bServerReady,bUserStatsBroken,bCurrentlyHealing; // SWAT Enforcer var array CurrentBumpedActors; // The unique list of actors that have been bumped before the last cooldown reset var float LastBumpTime; // The last time a zed was bumped using battering ram var float BumpCooldown; // The amount of time between when the last actor was bumped and another actor can be bumped again var float BumpMomentum; // Amount of momentum when bumping zeds var int BumpDamageAmount; // Amount of damage Battering Ram bumps deal var class BumpDamageType; // Damage type used for Battering Ram bump damage replication { // Things the server should send to the client. if (bNetDirty) CurrentPerk; } final function SetGrenadeCap(byte AddedCap) { MaxGrenadeCount = Default.MaxGrenadeCount + AddedCap; if (RepState==REP_Done) ClientSetGrenadeCap(MaxGrenadeCount); } simulated reliable client function ClientSetGrenadeCap(byte NewCap) { MaxGrenadeCount = NewCap; } function bool ApplyPerkClass(class P) { local int i; for (i=0; i HP.HealthMax) HP.Health = HP.HealthMax; if (HP.Armor > HP.MaxArmor) HP.Armor = HP.MaxArmor; } } } } simulated final function Ext_PerkBase FindPerk(class P) { local int i; for (i=0; i=0; --i) { UserPerks[i].PerkManager = None; UserPerks[i].Destroy(); } } function EarnedEXP(int EXP, optional byte Mode) { // `log("EarnedEXP" @ GetScriptTrace()); if (CurrentPerk!=None) { // Limit how much EXP we got for healing and welding. switch (Mode) { case 1: ExpUpStatus[0]+=EXP; EXP = ExpUpStatus[0]/CurrentPerk.WeldExpUpNum; if (EXP>0) ExpUpStatus[0]-=(EXP*CurrentPerk.WeldExpUpNum); break; case 2: ExpUpStatus[1]+=EXP; EXP = ExpUpStatus[1]/CurrentPerk.HealExpUpNum; if (EXP>0) ExpUpStatus[1]-=(EXP*CurrentPerk.HealExpUpNum); break; } if (EXP>0 && CurrentPerk.EarnedEXP(EXP)) { TotalEXP+=EXP; PRIOwner.RepEXP+=EXP; bStatsDirty = true; } } } // XML stat writing function OutputXML(ExtStatWriter Data) { local string S; local int i; Data.StartIntendent("user","ver",string(CUR_SaveVersion)); Data.WriteValue("id64",OnlineSubsystemSteamworks(class'GameEngine'.Static.GetOnlineSubsystem()).UniqueNetIdToInt64(PRIOwner.UniqueId)); Data.WriteValue("name",PRIOwner.PlayerName); Data.WriteValue("exp",string(TotalEXP)); Data.WriteValue("kills",string(TotalKills)); Data.WriteValue("time",string(TotalPlayTime)); if (ExtPlayerController(Owner)!=None && ExtPlayerController(Owner).PendingPerkClass!=None) S = string(ExtPlayerController(Owner).PendingPerkClass.Name); else S = (CurrentPerk!=None ? string(CurrentPerk.Class.Name) : "None"); Data.WriteValue("activeperk",S); for (i=0; i=PRIOwner.CustomCharList.Length) { PRIOwner.AllCharReceived(); RepIndex = 0; ++RepState; } else { PRIOwner.ReceivedCharacter(RepIndex,PRIOwner.CustomCharList[RepIndex]); ++RepIndex; } break; case REP_CustomInventory: // Replicate custom trader inventory if (!PRIOwner.OnRepNextItem(PRIOwner,RepIndex)) { RepIndex = 0; ++RepState; } else ++RepIndex; break; case REP_PerkClasses: // Open up all actor channel connections. if (RepIndex>=UserPerks.Length) { RepIndex = 0; ++RepState; } else if (UserPerks[RepIndex].bClientAuthorized) { if (UserPerks[RepIndex].bPerkNetReady) ++RepIndex; } else { UserPerks[RepIndex].RemoteRole = ROLE_SimulatedProxy; if (UserPerks[RepIndex].NextAuthTime DT) { return true; } simulated function ModifySpeed(out float Speed) { if (CurrentPerk!=None) Speed *= CurrentPerk.Modifiers[0]; } function ModifyDamageGiven(out int InDamage, optional Actor DamageCauser, optional KFPawn_Monster MyKFPM, optional KFPlayerController DamageInstigator, optional class DamageType, optional int HitZoneIdx) { if (CurrentPerk!=None) CurrentPerk.ModifyDamageGiven(InDamage,DamageCauser,MyKFPM,DamageInstigator,DamageType,HitZoneIdx); } simulated function ModifyDamageTaken(out int InDamage, optional class DamageType, optional Controller InstigatedBy) { if (CurrentPerk!=None) CurrentPerk.ModifyDamageTaken(InDamage,DamageType,InstigatedBy); } simulated function ModifyRecoil(out float CurrentRecoilModifier, KFWeapon KFW) { if (CurrentPerk!=None) CurrentPerk.ModifyRecoil(CurrentRecoilModifier,KFW); } simulated function ModifySpread(out float InSpread) { if (CurrentPerk!=None) CurrentPerk.ModifySpread(InSpread); } simulated function ModifyRateOfFire(out float InRate, KFWeapon KFW) { if (CurrentPerk!=None) CurrentPerk.ModifyRateOfFire(InRate,KFW); } simulated function float GetReloadRateScale(KFWeapon KFW) { return (CurrentPerk!=None ? CurrentPerk.GetReloadRateScale(KFW) : 1.f); } simulated function bool GetUsingTactialReload(KFWeapon KFW) { return (CurrentPerk!=None ? CurrentPerk.GetUsingTactialReload(KFW) : false); } function ModifyHealth(out int InHealth) { if (CurrentPerk!=None) CurrentPerk.ModifyHealth(InHealth); } function ModifyArmor(out byte MaxArmor) { if (CurrentPerk!=None) CurrentPerk.ModifyArmor(MaxArmor); } function float GetKnockdownPowerModifier(optional class DamageType, optional byte BodyPart, optional bool bIsSprinting=false) { return (CurrentPerk!=None ? CurrentPerk.GetKnockdownPowerModifier() : 1.f); } function float GetStumblePowerModifier(optional KFPawn KFP, optional class DamageType, optional out float CooldownModifier, optional byte BodyPart) { return (CurrentPerk!=None ? CurrentPerk.GetKnockdownPowerModifier() : 1.f); } function float GetStunPowerModifier(optional class DamageType, optional byte HitZoneIdx) { return (CurrentPerk!=None ? CurrentPerk.GetStunPowerModifier(DamageType,HitZoneIdx) : 1.f); } simulated function ModifyMeleeAttackSpeed(out float InDuration, KFWeapon KFW) { if (CurrentPerk!=None) CurrentPerk.ModifyMeleeAttackSpeed(InDuration); } simulated function class GetGrenadeClass() { return (CurrentPerk!=None ? CurrentPerk.GrenadeClass : GrenadeClass); } simulated function ModifyWeldingRate(out float FastenRate, out float UnfastenRate) { if (CurrentPerk!=None) CurrentPerk.ModifyWeldingRate(FastenRate,UnfastenRate); } simulated function bool HasNightVision() { return (CurrentPerk!=None ? CurrentPerk.bHasNightVision : false); } function bool RepairArmor(Pawn HealTarget) { return (CurrentPerk!=None ? CurrentPerk.RepairArmor(HealTarget) : false); } function bool ModifyHealAmount(out float HealAmount) { return (CurrentPerk!=None ? CurrentPerk.ModifyHealAmount(HealAmount) : false); } function bool CanNotBeGrabbed() { return (CurrentPerk!=None ? !CurrentPerk.bCanBeGrabbed : false); } simulated function ModifyMagSizeAndNumber(KFWeapon KFW, out int MagazineCapacity, optional array< Class > WeaponPerkClass, optional bool bSecondary=false, optional name WeaponClassname) { if (CurrentPerk!=None) CurrentPerk.ModifyMagSizeAndNumber(KFW,MagazineCapacity,WeaponPerkClass,bSecondary,WeaponClassname); } simulated function ModifySpareAmmoAmount(KFWeapon KFW, out int PrimarySpareAmmo, optional const out STraderItem TraderItem, optional bool bSecondary=false) { if (CurrentPerk!=None) CurrentPerk.ModifySpareAmmoAmount(KFW,PrimarySpareAmmo,TraderItem,bSecondary); } simulated function ModifyMaxSpareAmmoAmount(KFWeapon KFW, out int SpareAmmoCapacity, optional const out STraderItem TraderItem, optional bool bSecondary=false) { if (CurrentPerk!=None) CurrentPerk.ModifySpareAmmoAmount(KFW,SpareAmmoCapacity,TraderItem,bSecondary); } simulated function bool ShouldMagSizeModifySpareAmmo(KFWeapon KFW, optional Class WeaponPerkClass) { return (CurrentPerk!=None ? CurrentPerk.ShouldMagSizeModifySpareAmmo(KFW,WeaponPerkClass) : false); } simulated function ModifyHealerRechargeTime(out float RechargeRate) { if (CurrentPerk!=None) CurrentPerk.ModifyHealerRechargeTime(RechargeRate); } simulated function bool CanExplosiveWeld() { return (CurrentPerk!=None ? CurrentPerk.bExplosiveWeld : false); } simulated function bool IsOnContactActive() { return (CurrentPerk!=None ? CurrentPerk.bExplodeOnContact : false); } function bool CanSpreadNapalm() { if (CurrentPerk!=None && CurrentPerk.bNapalmFire && LastNapalmTime!=WorldInfo.TimeSeconds) { LastNapalmTime = WorldInfo.TimeSeconds; // Avoid infinite script recursion in KFPawn_Monster. return true; } return false; } simulated function bool IsRangeActive() { return MyPRI!=None ? MyPRI.bExtraFireRange : false; } simulated function DrawSpecialPerkHUD(Canvas C) { if (CurrentPerk!=None) CurrentPerk.DrawSpecialPerkHUD(C); } function PlayerKilled(KFPawn_Monster Victim, class DamageType) { if (CurrentPerk!=None) CurrentPerk.PlayerKilled(Victim,DamageType); } function ModifyBloatBileDoT(out float DoTScaler) { if (CurrentPerk!=None) CurrentPerk.ModifyBloatBileDoT(DoTScaler); } simulated function bool GetIsUberAmmoActive(KFWeapon KFW) { return (CurrentPerk!=None ? CurrentPerk.GetIsUberAmmoActive(KFW) : false); } function UpdatePerkHeadShots(ImpactInfo Impact, class DamageType, int NumHit) { if (CurrentPerk!=None) CurrentPerk.UpdatePerkHeadShots(Impact,DamageType,NumHit); } function CheckForAirborneAgent(KFPawn HealTarget, class DamType, int HealAmount) { if (!bCurrentlyHealing && CurrentPerk!=None) { // Using boolean to avoid infinite recursion. bCurrentlyHealing = true; CurrentPerk.CheckForAirborneAgent(HealTarget,DamType,HealAmount); bCurrentlyHealing = false; } } simulated function float GetZedTimeModifier(KFWeapon W) { return (CurrentPerk!=None ? CurrentPerk.GetZedTimeModifier(W) : 0.f); } // Poison darts function bool IsAcidicCompoundActive() { return (CurrentPerk!=None ? CurrentPerk.bToxicDart : false); } function ModifyACDamage(out int InDamage) { if (CurrentPerk!=None && CurrentPerk.bToxicDart) InDamage += CurrentPerk.ToxicDartDamage; } // Zombie explosion! function bool CouldBeZedShrapnel(class KFDT) { return (CurrentPerk!=None ? (CurrentPerk.bFireExplode && class(KFDT)!=None) : false); } simulated function bool ShouldShrapnel() { return (CurrentPerk!=None ? (CurrentPerk.bFireExplode && Rand(3)==0) : false); } function GameExplosion GetExplosionTemplate() { return class'KFPerk_Firebug'.Default.ExplosionTemplate; } // Additional functions function OnWaveEnded() { CurrentPerk.OnWaveEnded(); } function NotifyZedTimeStarted() { CurrentPerk.NotifyZedTimeStarted(); } simulated function float GetZedTimeExtensions(byte Level) { return CurrentPerk.GetZedTimeExtensions(Level); } // SWAT: simulated function bool HasHeavyArmor() { return (CurrentPerk!=None && CurrentPerk.bHeavyArmor); } simulated function float GetIronSightSpeedModifier(KFWeapon KFW) { return (CurrentPerk!=None ? CurrentPerk.GetIronSightSpeedModifier(KFW) : 1.f); } simulated function float GetCrouchSpeedModifier(KFWeapon KFW) { return (CurrentPerk!=None ? CurrentPerk.GetIronSightSpeedModifier(KFW) : 1.f); } simulated function bool ShouldKnockDownOnBump() { return (CurrentPerk!=None && CurrentPerk.bHasSWATEnforcer); } simulated function OnBump(Actor BumpedActor, KFPawn_Human BumpInstigator, vector BumpedVelocity, rotator BumpedRotation) { local KFPawn_Monster KFPM; local bool CanBump; if (ShouldKnockDownOnBump() && Normal(BumpedVelocity) dot Vector(BumpedRotation) > 0.7f) { KFPM = KFPawn_Monster(BumpedActor); if (KFPM != none) { // cooldown so that the same zed can't be bumped multiple frames back to back // especially relevant if they can't be knocked down or stumbled so the player is always bumping them if (WorldInfo.TimeSeconds - LastBumpTime > BumpCooldown) { CurrentBumpedActors.length = 0; CurrentBumpedActors.AddItem(BumpedActor); CanBump = true; } // if still within the cooldown time, can still bump the actor as long as it hasn't been bumped yet else if (CurrentBumpedActors.Find(BumpedActor) == INDEX_NONE) { CurrentBumpedActors.AddItem(BumpedActor); CanBump = true; } LastBumpTime = WorldInfo.TimeSeconds; if (CanBump) { if (KFPM.IsHeadless()) { KFPM.TakeDamage(KFPM.HealthMax, BumpInstigator.Controller, BumpInstigator.Location, Normal(vector(BumpedRotation)) * BumpMomentum, BumpDamageType); } else { KFPM.TakeDamage(BumpDamageAmount, BumpInstigator.Controller, BumpInstigator.Location, Normal(vector(BumpedRotation)) * BumpMomentum, BumpDamageType); KFPM.Knockdown(BumpedVelocity * 3, vect(1, 1, 1), KFPM.Location, 1000, 100); } } } } } // DEMO: simulated function bool ShouldRandSirenResist() { return (Ext_PerkDemolition(CurrentPerk)!=None ? Ext_PerkDemolition(CurrentPerk).bSirenResistance : false); } simulated function bool IsAoEActive() { return (Ext_PerkDemolition(CurrentPerk)!=None ? Ext_PerkDemolition(CurrentPerk).AOEMult > 1.0f : false); } simulated function bool ShouldSacrifice() { return (Ext_PerkDemolition(CurrentPerk)!=None ? (Ext_PerkDemolition(CurrentPerk).bCanUseSacrifice && !Ext_PerkDemolition(CurrentPerk).bUsedSacrifice) : false); } simulated function bool ShouldNeverDud() { return (Ext_PerkDemolition(CurrentPerk)!=None ? Ext_PerkDemolition(CurrentPerk).bProfessionalActive : false); } function NotifyPerkSacrificeExploded() { if (Ext_PerkDemolition(CurrentPerk) != none) Ext_PerkDemolition(CurrentPerk).bUsedSacrifice = true; } simulated function float GetAoERadiusModifier() { return (Ext_PerkDemolition(CurrentPerk)!=None ? Ext_PerkDemolition(CurrentPerk).GetAoERadiusModifier() : 1.0); } // MEDIC: simulated function bool GetHealingSpeedBoostActive() { return (Ext_PerkFieldMedic(CurrentPerk)!=None ? Ext_PerkFieldMedic(CurrentPerk).GetHealingSpeedBoostActive() : false); } simulated function bool GetHealingDamageBoostActive() { return (Ext_PerkFieldMedic(CurrentPerk)!=None ? Ext_PerkFieldMedic(CurrentPerk).GetHealingDamageBoostActive() : false); } simulated function bool GetHealingShieldActive() { return (Ext_PerkFieldMedic(CurrentPerk)!=None ? Ext_PerkFieldMedic(CurrentPerk).GetHealingShieldActive() : false); } simulated function float GetSelfHealingSurgePct() { return (Ext_PerkFieldMedic(CurrentPerk)!=None ? Ext_PerkFieldMedic(CurrentPerk).GetSelfHealingSurgePct() : 0.f); } function bool IsToxicDmgActive() { return (Ext_PerkFieldMedic(CurrentPerk)!=None ? Ext_PerkFieldMedic(CurrentPerk).bUseToxicDamage : false); } static function class GetToxicDmgTypeClass() { return class'Ext_PerkFieldMedic'.static.GetToxicDmgTypeClass(); } static function ModifyToxicDmg(out int ToxicDamage) { ToxicDamage = class'Ext_PerkFieldMedic'.static.ModifyToxicDmg(ToxicDamage); } simulated function float GetSnarePower(optional class DamageType, optional byte HitZoneIdx) { return (Ext_PerkFieldMedic(CurrentPerk)!=None ? Ext_PerkFieldMedic(CurrentPerk).GetSnarePower(DamageType, HitZoneIdx) : 0.f); } // SUPPORT: simulated function bool CanRepairDoors() { return (Ext_PerkSupport(CurrentPerk)!=None ? Ext_PerkSupport(CurrentPerk).CanRepairDoors() : false); } simulated function float GetPenetrationModifier(byte Level, class DamageType, optional bool bForce ) { return (Ext_PerkSupport(CurrentPerk)!=None ? Ext_PerkSupport(CurrentPerk).GetPenetrationModifier(Level, DamageType, bForce) : 0.f); } // SwitchSpeed simulated function ModifyWeaponSwitchTime( out float ModifiedSwitchTime ) { if( CurrentPerk!=None ) CurrentPerk.ModifyWeaponSwitchTime(ModifiedSwitchTime); } // Other function ApplySkillsToPawn() { if (CheckOwnerPawn()) { OwnerPawn.UpdateGroundSpeed(); OwnerPawn.bMovesFastInZedTime = false; if (MyPRI == none) MyPRI = KFPlayerReplicationInfo(OwnerPawn.PlayerReplicationInfo); ApplyWeightLimits(); } } defaultproperties { bTickIsDisabled=false NetPriority=3.5 // SWAT bumping BumpCooldown = 0.1f BumpMomentum=1.f BumpDamageAmount=450 BumpDamageType=class'KFDT_SWATBatteringRam' }