928 lines
23 KiB
Ucode
928 lines
23 KiB
Ucode
/*
|
|
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<Ext_PerkBase> 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<Actor> 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<DamageType> 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<Ext_PerkBase> P)
|
|
{
|
|
local int i;
|
|
|
|
for (i=0; i<UserPerks.Length; ++i)
|
|
if (UserPerks[i].Class==P)
|
|
{
|
|
ApplyPerk(UserPerks[i]);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function bool ApplyPerkName(string S)
|
|
{
|
|
local int i;
|
|
|
|
for (i=0; i<UserPerks.Length; ++i)
|
|
if (string(UserPerks[i].Class.Name)~=S)
|
|
{
|
|
ApplyPerk(UserPerks[i]);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function ApplyPerk(Ext_PerkBase P)
|
|
{
|
|
local KFPawn_Human HP;
|
|
local KFInventoryManager InvMan;
|
|
local Ext_T_ZEDHelper H;
|
|
local int i;
|
|
|
|
if (P==None)
|
|
return;
|
|
|
|
if (PlayerOwner.Pawn != None)
|
|
{
|
|
InvMan = KFInventoryManager(PlayerOwner.Pawn.InvManager);
|
|
if (InvMan != None)
|
|
InvMan.MaxCarryBlocks = InvMan.Default.MaxCarryBlocks;
|
|
|
|
foreach PlayerOwner.Pawn.ChildActors(class'Ext_T_ZEDHelper',H)
|
|
{
|
|
H.Destroy();
|
|
}
|
|
|
|
HP = KFPawn_Human(PlayerOwner.Pawn);
|
|
if (HP != None)
|
|
HP.DefaultInventory = HP.Default.DefaultInventory;
|
|
}
|
|
|
|
if (CurrentPerk != None)
|
|
{
|
|
CurrentPerk.DeactivateTraits();
|
|
|
|
for (i=0; i<CurrentPerk.PerkTraits.Length; ++i)
|
|
{
|
|
CurrentPerk.PerkTraits[i].TraitType.Static.CancelEffectOn(KFPawn_Human(PlayerOwner.Pawn),CurrentPerk,CurrentPerk.PerkTraits[i].CurrentLevel,CurrentPerk.PerkTraits[i].Data);
|
|
}
|
|
}
|
|
|
|
bStatsDirty = true;
|
|
CurrentPerk = P;
|
|
|
|
if (PRIOwner!=None)
|
|
{
|
|
PRIOwner.ECurrentPerk = P.Class;
|
|
PRIOwner.FCurrentPerk = P;
|
|
PRIOwner.CurrentPerkClass = P.BasePerk;
|
|
P.UpdatePRILevel();
|
|
}
|
|
|
|
if (CurrentPerk!=None)
|
|
{
|
|
CurrentPerk.ActivateTraits();
|
|
|
|
if (PlayerOwner.Pawn != None)
|
|
{
|
|
HP = KFPawn_Human(PlayerOwner.Pawn);
|
|
if (HP != None)
|
|
{
|
|
HP.HealthMax = HP.default.Health;
|
|
HP.MaxArmor = HP.default.MaxArmor;
|
|
|
|
ModifyHealth(HP.HealthMax);
|
|
ModifyArmor(HP.MaxArmor);
|
|
CurrentPerk.UpdateAmmoStatus(HP.InvManager);
|
|
|
|
if (HP.Health > HP.HealthMax) HP.Health = HP.HealthMax;
|
|
if (HP.Armor > HP.MaxArmor) HP.Armor = HP.MaxArmor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
simulated final function Ext_PerkBase FindPerk(class<Ext_PerkBase> P)
|
|
{
|
|
local int i;
|
|
|
|
for (i=0; i<UserPerks.Length; ++i)
|
|
if (UserPerks[i].Class==P)
|
|
return UserPerks[i];
|
|
return None;
|
|
}
|
|
|
|
simulated function PostBeginPlay()
|
|
{
|
|
SetTimer(0.01,false,'InitPerks');
|
|
if (WorldInfo.NetMode!=NM_Client)
|
|
SetTimer(1,true,'CheckPlayTime');
|
|
}
|
|
|
|
simulated function InitPerks()
|
|
{
|
|
local Ext_PerkBase P;
|
|
|
|
if (WorldInfo.NetMode==NM_Client)
|
|
{
|
|
foreach DynamicActors(class'Ext_PerkBase',P)
|
|
if (P.PerkManager!=Self)
|
|
RegisterPerk(P);
|
|
}
|
|
else if (PRIOwner!=PlayerOwner.PlayerReplicationInfo) // See if was assigned an inactive PRI.
|
|
{
|
|
PRIOwner = ExtPlayerReplicationInfo(PlayerOwner.PlayerReplicationInfo);
|
|
if (PRIOwner!=None)
|
|
{
|
|
if (CurrentPerk!=None)
|
|
{
|
|
PRIOwner.ECurrentPerk = CurrentPerk.Class;
|
|
PRIOwner.CurrentPerkClass = CurrentPerk.BasePerk;
|
|
CurrentPerk.UpdatePRILevel();
|
|
}
|
|
PRIOwner.RepKills = TotalKills;
|
|
PRIOwner.RepEXP = TotalEXP;
|
|
PRIOwner.SetInitPlayTime(TotalPlayTime);
|
|
PRIOwner.PerkManager = Self;
|
|
}
|
|
}
|
|
}
|
|
|
|
function CheckPlayTime()
|
|
{
|
|
++TotalPlayTime; // Stats.
|
|
}
|
|
|
|
function ServerInitPerks()
|
|
{
|
|
local int i;
|
|
|
|
for (i=0; i<UserPerks.Length; ++i)
|
|
UserPerks[i].SetInitialLevel();
|
|
bServerReady = true;
|
|
CurrentPerk = None;
|
|
if (StrPerkName!="")
|
|
ApplyPerkName(StrPerkName);
|
|
if (CurrentPerk==None)
|
|
ApplyPerk(UserPerks[Rand(UserPerks.Length)]);
|
|
}
|
|
|
|
simulated function RegisterPerk(Ext_PerkBase P)
|
|
{
|
|
UserPerks[UserPerks.Length] = P;
|
|
P.PerkManager = Self;
|
|
}
|
|
|
|
simulated function UnregisterPerk(Ext_PerkBase P)
|
|
{
|
|
UserPerks.RemoveItem(P);
|
|
P.PerkManager = None;
|
|
}
|
|
|
|
function Destroyed()
|
|
{
|
|
local int i;
|
|
|
|
for (i=(UserPerks.Length-1); 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<UserPerks.Length; ++i)
|
|
if (UserPerks[i].HasAnyProgress())
|
|
UserPerks[i].OutputXML(Data);
|
|
|
|
Data.EndIntendent();
|
|
}
|
|
|
|
// Data saving.
|
|
function SaveData(ExtSaveDataBase Data)
|
|
{
|
|
local int i,o;
|
|
|
|
Data.FlushData();
|
|
Data.SetSaveVersion(++UserDataVersion);
|
|
Data.SetArVer(CUR_SaveVersion);
|
|
|
|
// Write global stats.
|
|
Data.SaveInt(TotalEXP,3);
|
|
Data.SaveInt(TotalKills,3);
|
|
Data.SaveInt(TotalPlayTime,3);
|
|
|
|
// Write character.
|
|
if (PRIOwner!=None)
|
|
PRIOwner.SaveCustomCharacter(Data);
|
|
else class'ExtPlayerReplicationInfo'.Static.DummySaveChar(Data);
|
|
|
|
// Write selected perk.
|
|
if (ExtPlayerController(Owner)!=None && ExtPlayerController(Owner).PendingPerkClass!=None)
|
|
Data.SaveStr(string(ExtPlayerController(Owner).PendingPerkClass.Name));
|
|
else Data.SaveStr(CurrentPerk!=None ? string(CurrentPerk.Class.Name) : "");
|
|
|
|
// Count how many progressed perks we have.
|
|
o = 0;
|
|
for (i=0; i<UserPerks.Length; ++i)
|
|
if (UserPerks[i].HasAnyProgress())
|
|
++o;
|
|
|
|
// Then write count we have.
|
|
Data.SaveInt(o);
|
|
|
|
// Then perk stats.
|
|
for (i=0; i<UserPerks.Length; ++i)
|
|
{
|
|
if (!UserPerks[i].HasAnyProgress()) // Skip this perk.
|
|
continue;
|
|
|
|
Data.SaveStr(string(UserPerks[i].Class.Name));
|
|
o = Data.TellOffset(); // Mark checkpoint.
|
|
Data.SaveInt(0,1); // Reserve space for later.
|
|
UserPerks[i].SaveData(Data);
|
|
|
|
// Now save the skip offset for perk data incase perk gets removed from server.
|
|
Data.SeekOffset(o);
|
|
Data.SaveInt(Data.TotalSize(),1);
|
|
Data.ToEnd();
|
|
}
|
|
}
|
|
|
|
// Data loading.
|
|
function LoadData(ExtSaveDataBase Data)
|
|
{
|
|
local int i,j,l,o;
|
|
local string S;
|
|
|
|
Data.ToStart();
|
|
UserDataVersion = Data.GetSaveVersion();
|
|
|
|
// Read global stats.
|
|
TotalEXP = Data.ReadInt(3);
|
|
TotalKills = Data.ReadInt(3);
|
|
TotalPlayTime = Data.ReadInt(3);
|
|
|
|
// Read character.
|
|
if (PRIOwner!=None)
|
|
{
|
|
PRIOwner.RepKills = TotalKills;
|
|
PRIOwner.RepEXP = TotalEXP;
|
|
PRIOwner.SetInitPlayTime(TotalPlayTime);
|
|
PRIOwner.LoadCustomCharacter(Data);
|
|
}
|
|
else class'ExtPlayerReplicationInfo'.Static.DummyLoadChar(Data);
|
|
|
|
// Find selected perk.
|
|
CurrentPerk = None;
|
|
StrPerkName = Data.ReadStr();
|
|
|
|
l = Data.ReadInt(); // Perk stats length.
|
|
for (i=0; i<l; ++i)
|
|
{
|
|
S = Data.ReadStr();
|
|
o = Data.ReadInt(1); // Read skip offset.
|
|
Data.PushEOFLimit(o);
|
|
for (j=0; j<UserPerks.Length; ++j)
|
|
if (S~=string(UserPerks[j].Class.Name))
|
|
{
|
|
UserPerks[j].LoadData(Data);
|
|
break;
|
|
}
|
|
Data.PopEOFLimit();
|
|
Data.SeekOffset(o); // Jump to end of this section.
|
|
}
|
|
bStatsDirty = false;
|
|
}
|
|
|
|
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 (CurrentPerk!=None)
|
|
CurrentPerk.AddDefaultInventory(P);
|
|
}
|
|
}
|
|
|
|
simulated function PlayerDied()
|
|
{
|
|
if (CurrentPerk!=None)
|
|
CurrentPerk.PlayerDied();
|
|
}
|
|
|
|
function PreNotifyPlayerLeave()
|
|
{
|
|
if (CurrentPerk!=None)
|
|
CurrentPerk.DeactivateTraits();
|
|
}
|
|
|
|
// Start client replication of perks data.
|
|
// Call this once the stats has been properly loaded serverside!
|
|
function InitiateClientRep()
|
|
{
|
|
RepState = 0;
|
|
RepIndex = 0;
|
|
SetTimer(0.01,true,'ReplicateTimer');
|
|
}
|
|
|
|
function ReplicateTimer()
|
|
{
|
|
switch (RepState)
|
|
{
|
|
case REP_CustomCharacters: // Replicate custom characters.
|
|
if (RepIndex>=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<WorldInfo.RealTimeSeconds)
|
|
{
|
|
UserPerks[RepIndex].NextAuthTime = WorldInfo.RealTimeSeconds+0.5;
|
|
UserPerks[RepIndex].ClientAuth();
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
if (MaxGrenadeCount!=Default.MaxGrenadeCount)
|
|
ClientSetGrenadeCap(MaxGrenadeCount);
|
|
ClearTimer('ReplicateTimer');
|
|
}
|
|
}
|
|
|
|
function bool CanEarnSmallRadiusKillXP(class<DamageType> 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<KFDamageType> DamageType, optional int HitZoneIdx)
|
|
{
|
|
if (CurrentPerk!=None)
|
|
CurrentPerk.ModifyDamageGiven(InDamage,DamageCauser,MyKFPM,DamageInstigator,DamageType,HitZoneIdx);
|
|
}
|
|
|
|
simulated function ModifyDamageTaken(out int InDamage, optional class<DamageType> 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 float GetCameraViewShakeModifier(KFWeapon KFW)
|
|
{
|
|
return (CurrentPerk!=None ? CurrentPerk.GetCameraViewShakeModifier(KFW) : 1.f);
|
|
}
|
|
|
|
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> DamageType, optional byte BodyPart, optional bool bIsSprinting=false)
|
|
{
|
|
return (CurrentPerk!=None ? CurrentPerk.GetKnockdownPowerModifier() : 1.f);
|
|
}
|
|
|
|
function float GetStumblePowerModifier(optional KFPawn KFP, optional class<KFDamageType> DamageType, optional out float CooldownModifier, optional byte BodyPart)
|
|
{
|
|
return (CurrentPerk!=None ? CurrentPerk.GetKnockdownPowerModifier() : 1.f);
|
|
}
|
|
|
|
function float GetStunPowerModifier(optional class<DamageType> 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<KFProj_Grenade> 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<KFPerk> > 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<KFPerk> 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> 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> DamageType, int NumHit)
|
|
{
|
|
if (CurrentPerk!=None)
|
|
CurrentPerk.UpdatePerkHeadShots(Impact,DamageType,NumHit);
|
|
}
|
|
|
|
function CheckForAirborneAgent(KFPawn HealTarget, class<DamageType> 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<KFDamageType> KFDT)
|
|
{
|
|
return (CurrentPerk!=None ? (CurrentPerk.bFireExplode && class<KFDT_Fire>(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<KFDamageType> 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> 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<KFDamageType> DamageType, optional bool bForce )
|
|
{
|
|
return (Ext_PerkSupport(CurrentPerk)!=None ? Ext_PerkSupport(CurrentPerk).GetPenetrationModifier(Level, DamageType, bForce) : 0.f);
|
|
}
|
|
|
|
// 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'
|
|
}
|