rott/kf_sources/ScrnBalanceSrv/Classes/ScrnHumanPawn.uc
2026-07-14 20:27:09 +07:00

2591 lines
91 KiB
Ucode

class ScrnHumanPawn extends SRHumanPawn;
var float HealthRestoreRate; // how fast player can be healed (hp/s)
var float HealthToGiveRemainder; // pawn receives integer amount of hp. remainder should be moved to next healing tick
var byte ClientHealthToGive; //required for client replication
var const class<ScrnVestPickup> NoVestClass; // dummy class that indicates player has no armor
var const class<ScrnVestPickup> StandardVestClass; // standard KF armor (combat armor)
var const class<ScrnVestPickup> LightVestClass; // Warning! LightVestClass must have no weight (weight=0)
var private class<ScrnVestPickup> CurrentVestClass; // Equipped shield class
var private transient class<KFVeterancyTypes> PrevPerkClass;
var private transient int PrevPerkLevel;
var bool bCowboyMode;
var transient ScrnHumanPawn LastHealedBy; // last player who healed me
var transient ScrnHumanPawn LastHealed; // last player, who was healed by me
var transient KFMonster CombatMedicTarget; // "LastHealedBy" must kill this monster to earn an ach
var transient int HealthBeforeHealing;
var transient float LastDamageTime;
var transient float LastExplosionTime;
var transient float LastExplosionDistance; // distance between player and explosions's epicenter
// Seems like bonus ammo is fixed in v1051 and not needed anymore
// var class<Ammunition> BonusAmmoClass;
// var int BonusAmmoAmount;
var ScrnGameRules GameRules;
var bool bCheckHorzineArmorAch;
// used in AssessThreatTo()
var protected transient KFMonsterController LastThreatMonster;
var protected transient float LastThreatTime, LastThreat;
var localized string strNoSpawnCashToss;
struct SWeaponFlashlight {
var class<KFWeapon> WeaponClass;
var int TorchBatteryLife, MaxBatteryLife;
};
var array<SWeaponFlashlight> WeaponFlashlights;
var Sound HeadshotSound;
var transient int HealthBeforeDeath; // in cases when death is not caused by health drop down to 0
var transient float NextEnemyBaseDamageMsg; //reserved for TSC
var() int HealthBonus;
var KFPlayerReplicationInfo KFPRI;
var class<ScrnVeterancyTypes> ScrnPerk;
var transient bool bAmIBaron; // :trollface:
// spectator info
var bool bViewTarget; // somebody spectating me
var class<KFWeapon> SpecWeapon;
var byte AmmoStatus;
var byte SpecWeight, SpecMagAmmo, SpecMags, SpecSecAmmo, SpecNades;
var transient Frag PlayerGrenade;
var bool bTraderSpeedBoost;
var float TraderSpeedBoost;
var byte MacheteBoost; // that's one of the most retarded things I've done
var float MacheteResetTime;
var transient KFMeleeGun QuickMeleeWeapon;
var transient KFWeapon WeaponToFixClientState;
var transient bool bQuickMeleeInProgress;
var localized string BlameStrM99;
var localized string BlameStrRPG;
replication
{
reliable if( bNetOwner && Role == ROLE_Authority )
ClientSetInventoryGroup;
reliable if( bNetOwner && Role == ROLE_Authority )
QuickMeleeWeapon, MacheteBoost;
reliable if( Role == ROLE_Authority )
ClientSetVestClass; //send it to all clients, cuz they need to know max health and max shield
reliable if( bNetDirty && Role == ROLE_Authority )
ClientHealthToGive, HealthBonus; // all clients need to know it to properly display it on the hud
reliable if( bNetDirty && Role == ROLE_Authority )
SpecWeapon, AmmoStatus;
reliable if( bNetDirty && bViewTarget && Role == ROLE_Authority )
SpecWeight, SpecMagAmmo, SpecMags, SpecSecAmmo, SpecNades;
// seem like that there is no need to replicate bCowboyMode, because it is used only on local player,
// which can set it himself
// reliable if( !bNetOwner && bNetDirty && Role == ROLE_Authority )
// bCowboyMode; // net owner can check cowboy mode himself
reliable if(Role < ROLE_Authority)
ServerBuyShield, ServerSellShield;
reliable if(Role < ROLE_Authority)
ServerReload, ServerFire;
}
simulated function PostBeginPlay()
{
super.PostBeginPlay();
FindGameRules();
ReplaceRequiredEquipment();
if ( SoundGroupClass == none )
SoundGroupClass = Class'KFMod.KFMaleSoundGroup';
HealthMax += HealthBonus;
Health += HealthBonus;
bTraderSpeedBoost = class'ScrnBalance'.default.Mut.bTraderSpeedBoost;
}
function ReplaceRequiredEquipment()
{
local int i;
local KFLevelRules_Story StoryRules;
local ScrnBalance Mut;
Mut = class'ScrnBalance'.static.Myself(Level);
if ( KFStoryGameInfo(Level.Game) != none ) {
StoryRules = KFStoryGameInfo(Level.Game).StoryRules;
for ( i = 0; i < StoryRules.RequiredPlayerEquipment.Length; ++i ) {
if ( StoryRules.RequiredPlayerEquipment[i] == Class'KFMod.Knife' )
StoryRules.RequiredPlayerEquipment[i] = class'ScrnBalanceSrv.ScrnKnife';
else if ( StoryRules.RequiredPlayerEquipment[i] == Class'KFMod.Single' )
StoryRules.RequiredPlayerEquipment[i] = class'ScrnBalanceSrv.ScrnSingle';
else if ( Mut.bReplaceNades && StoryRules.RequiredPlayerEquipment[i] == Class'KFMod.Frag' )
StoryRules.RequiredPlayerEquipment[i] = class'ScrnBalanceSrv.ScrnFrag';
else if ( StoryRules.RequiredPlayerEquipment[i] == Class'KFMod.Syringe' )
StoryRules.RequiredPlayerEquipment[i] = class'ScrnBalanceSrv.ScrnSyringe';
}
}
else if ( Mut.bNoRequiredEquipment ) {
for ( i=0; i<16; ++i )
RequiredEquipment[i] = "";
}
else {
RequiredEquipment[0] = String(class'ScrnBalanceSrv.ScrnKnife');
RequiredEquipment[1] = String(class'ScrnBalanceSrv.ScrnSingle');
if ( Mut.bReplaceNades ) {
RequiredEquipment[2] = String(class'ScrnBalanceSrv.ScrnFrag');
}
RequiredEquipment[3] = String(class'ScrnBalanceSrv.ScrnSyringe');
}
}
function RecalcWeight()
{
local Inventory Inv;
local KFWeapon Weap;
local int c;
CurrentWeight = CurrentVestClass.default.Weight;
for ( Inv = Inventory; Inv != none && ++c < 1000; Inv = Inv.Inventory ) {
Weap = KFWeapon(Inv);
if (Weap == none)
continue;
if ( ScrnFrag(Weap) != none )
Weap.Weight = Weap.default.Weight;
CurrentWeight += Weap.Weight;
}
}
// Changed MaxCarryWeight to default.MaxCarryWeight, so support with 15/24 weight will move with same speed as other perk 15/15
// Support with 24/24 weight now will move slower
// Other code strings are just copy-pasted
simulated function ModifyVelocity(float DeltaTime, vector OldVelocity)
{
local float WeightMod, HealthMod, MovementMod;
local float EncumbrancePercentage;
local Inventory Inv;
local KF_StoryInventoryItem StoryInv;
local int c;
super(KFPawn).ModifyVelocity(DeltaTime, OldVelocity);
if ( Controller != none )
{
// Calculate encumbrance, but cap it to the maxcarryweight so when we use dev weapon cheats we don't move mega slow
EncumbrancePercentage = (FMin(CurrentWeight, MaxCarryWeight) / default.MaxCarryWeight); //changed MaxCarryWeight to default.MaxCarryWeight
// Calculate the weight modifier to speed
WeightMod = (1.0 - (EncumbrancePercentage * WeightSpeedModifier));
// Calculate the health modifier to speed
HealthMod = ((Health/HealthMax) * HealthSpeedModifier) + (1.0 - HealthSpeedModifier);
// Apply all the modifiers
GroundSpeed = default.GroundSpeed * HealthMod;
GroundSpeed *= WeightMod;
GroundSpeed += InventorySpeedModifier;
if ( KFPRI != none && KFPRI.ClientVeteranSkill != none )
MovementMod = KFPRI.ClientVeteranSkill.static.GetMovementSpeedModifier(KFPRI, KFGameReplicationInfo(Level.GRI));
else
MovementMod = 1.0;
GroundSpeed *= MovementMod;
AccelRate = default.AccelRate * MovementMod;
/* Give the pawn's inventory items a chance to modify his movement speed */
for( Inv=Inventory; Inv!=None && ++c < 1000; Inv=Inv.Inventory )
{
GroundSpeed *= Inv.GetMovementModifierFor(self);
StoryInv = KF_StoryInventoryItem(Inv);
if(StoryInv != none && StoryInv.bUseForcedGroundSpeed)
{
GroundSpeed = StoryInv.ForcedGroundSpeed;
return;
}
}
if ( bTraderSpeedBoost && !KFGameReplicationInfo(Level.GRI).bWaveInProgress )
GroundSpeed *= TraderSpeedBoost;
GroundSpeed += MacheteBoost;
}
}
function PossessedBy(Controller C)
{
Super.PossessedBy(C);
KFPRI = KFPlayerReplicationInfo(PlayerReplicationInfo);
ScrnPerk = class<ScrnVeterancyTypes>(KFPRI.ClientVeteranSkill);
if ( PlayerController(C) != none )
bAmIBaron = PlayerController(C).GetPlayerIDHash() == "76561198006289592";
}
function UnPossessed()
{
super.UnPossessed();
KFPRI = none;
ScrnPerk = none;
}
simulated function ClientSetInventoryGroup( class<Inventory> NewInventoryClass, byte NewInventoryGroup )
{
local Inventory Inv;
local int c;
if ( Role < ROLE_Authority ) {
//need to change default value to, because it is using in some static functions
NewInventoryClass.default.InventoryGroup = NewInventoryGroup;
//search the client inventory for the specified class and change its group
for ( Inv=Inventory; Inv!=None && ++c < 1000 ; Inv=Inv.Inventory ) {
if ( Inv.class == NewInventoryClass ) {
Inv.InventoryGroup = NewInventoryGroup;
return;
}
}
}
}
// handle pickup
// function HandlePickup(Pickup pick)
// {
// super.HandlePickup()
// }
function bool AddInventory( inventory NewItem )
{
local KFWeapon weap;
local bool GroupChanged;
// local KFAmmunition ammo;
weap = KFWeapon(NewItem);
if( weap != none ) {
//log("AddInventory:" @ weap, 'ScrnBalance');
if ( Dualies(weap) != none ) {
if ( (DualDeagle(weap) != none || Dual44Magnum(weap) != none || DualMK23Pistol(weap) != none)
&& weap.InventoryGroup != 4 ) { //skip special weapons like Laser-44
// Move dual pistols to slot 3 for Gunslinger perk, so he can easier to cycle them
if ( KFPRI != none && ClassIsChildOf(KFPRI.ClientVeteranSkill, class'ScrnBalanceSrv.ScrnVetGunslinger') )
weap.InventoryGroup = 3;
else
weap.InventoryGroup = 2; //leave dual pistols in slot 2 for other perks
GroupChanged = true;
}
}
else if ( weap.class == class'Single' ) {
weap.bKFNeverThrow = false;
}
weap.bIsTier3Weapon = true; // hack to set weap.Tier3WeaponGiver for all weapons
}
//replicate changes on the client side
if ( GroupChanged )
ClientSetInventoryGroup(NewItem.class, NewItem.InventoryGroup);
if ( super.AddInventory(NewItem) ) {
if ( weap != none ) {
if ( weap.bTorchEnabled )
AddToFlashlightArray(weap.class); // v6.22 - each weapon has own flashlight
CheckQuickMeleeWeapon(KFMeleeGun(weap));
if ( class'ScrnBalance'.default.Mut.SrvTourneyMode == 0 && Machete(weap) != none ) {
if ( MacheteBoost < 250 && VSizeSquared(Velocity) > 10000 ) {
if ( MacheteBoost < 50 )
MacheteBoost += 3;
else if ( MacheteBoost < 100 )
MacheteBoost += 2;
else
MacheteBoost++;
}
MacheteResetTime = Level.TimeSeconds + 5.0;
}
}
return true;
}
return false;
}
function DeleteInventory( inventory Item )
{
super.DeleteInventory(Item);
if ( Item == QuickMeleeWeapon ) {
QuickMeleeWeapon = none;
SetBestQuickMeleeWeapon();
// for machete-walking
if ( QuickMeleeWeapon != none )
PendingWeapon = QuickMeleeWeapon;
}
}
// todo: add support for modded guns
function bool ValidQuickMeleeWeapon(KFMeleeGun W)
{
if ( W == none )
return false;
return ScrnKnife(W) != none || ScrnMachete(W) != none;
}
function bool CheckQuickMeleeWeapon(KFMeleeGun W)
{
if ( !ValidQuickMeleeWeapon(W) )
return false;
if ( QuickMeleeWeapon == none || class<KFMeleeFire>(QuickMeleeWeapon.FireModeClass[1]).default.MeleeDamage <
class<KFMeleeFire>(W.FireModeClass[1]).default.MeleeDamage )
{
QuickMeleeWeapon = W;
return true;
}
return false;
}
function SetBestQuickMeleeWeapon()
{
local inventory inv;
local KFMeleeGun W;
local int c;
for ( inv = Inventory; inv != none && ++c < 1000; inv = inv.Inventory) {
W = KFMeleeGun(inv);
if ( W != none )
CheckQuickMeleeWeapon(W);
}
}
// The player wants to switch to weapon group number F.
// Merged code from Pawn and KFHumanPawn + added ability to switch perked weapon first and empty guns last
simulated function SwitchWeapon(byte F)
{
local Inventory Inv;
local Weapon W;
local bool bPerkedFirst;
local array<Weapon> SortedGroupInv; // perked -> non-perked -> empy
local int NonPerkedIndex, EmptyIndex, i;
local class<KFWeaponPickup> WP;
if ( (Level.Pauser!=None) || (Inventory == None) )
return;
if ( PendingWeapon != None && PendingWeapon.bForceSwitch )
return;
if ( bQuickMeleeInProgress )
return;
bPerkedFirst = ScrnPerk != none && ScrnPlayerController(Controller) != none && ScrnPlayerController(Controller).bPrioritizePerkedWeapons;
// sort group inventory
for ( Inv = Inventory; Inv != none && ++i < 1000; Inv = Inv.Inventory ) {
W = Weapon(Inv);
if ( W != none && W.InventoryGroup == F && AllowHoldWeapon(W) ) {
WP = class<KFWeaponPickup>(W.PickupClass);
if ( !W.HasAmmo() && (KFWeapon(W) == none || (KFWeapon(W).bConsumesPhysicalAmmo && !KFWeapon(W).bMeleeWeapon)) ) {
// weapon has no ammo
SortedGroupInv[SortedGroupInv.length] = W;
}
else if ( bPerkedFirst && (PipeBombExplosive(W) != none || Knife(W) != none
|| WP == none
|| (WP.default.CorrespondingPerkIndex != ScrnPerk.default.PerkIndex
&& !ScrnPerk.static.OverridePerkIndex(WP))) )
{
// non-perked weapon, has ammo
SortedGroupInv.insert(EmptyIndex, 1);
SortedGroupInv[EmptyIndex] = W;
EmptyIndex++;
}
else {
// perked weapon, has ammo
if ( Boomstick(W) != none && ScrnPlayerController(Controller) != none && ScrnPlayerController(Controller).bPrioritizeBoomstick ) {
SortedGroupInv.insert(0, 1);
SortedGroupInv[0] = W;
}
else {
SortedGroupInv.insert(NonPerkedIndex, 1);
SortedGroupInv[NonPerkedIndex] = W;
}
NonPerkedIndex++;
EmptyIndex++;
}
}
}
if ( SortedGroupInv.length == 0 )
return; // no weapons in current category
if ( Weapon == none || Weapon.InventoryGroup != F || SortedGroupInv.length == 1 )
W = SortedGroupInv[0];
else {
for ( i=0; i<SortedGroupInv.length; ++i ) {
if ( Weapon == SortedGroupInv[i] ) {
i++; // switch to next weapon
break;
}
}
if ( i < SortedGroupInv.length )
W = SortedGroupInv[i];
else
W = SortedGroupInv[0];
}
if ( W != none && W != Weapon ) {
PendingWeapon = W;
if ( Weapon != None )
Weapon.PutDown();
else
ChangedWeapon();
}
}
simulated function ChangedWeapon()
{
super(KFPawn).ChangedWeapon();
if (Role < ROLE_Authority) {
ApplyWeaponStats(Weapon);
}
if ( IsLocallyControlled() && ScrnPlayerController(Controller) != none )
ScrnPlayerController(Controller).LoadGunSkinFromConfig();
}
function ServerChangedWeapon(Weapon OldWeapon, Weapon NewWeapon)
{
local ScrnPlayerController PC;
local int i;
PC = ScrnPlayerController(Controller);
if ( NewWeapon != none && PC != none) {
// set skinned attachment class
for ( i=0; i<PC.WeaponSettings.length; ++i ) {
if ( NewWeapon.class == PC.WeaponSettings[i].Weapon ) {
if ( PC.WeaponSettings[i].SkinnedWeapon != none )
NewWeapon.AttachmentClass = PC.WeaponSettings[i].SkinnedWeapon.default.AttachmentClass;
break;
}
}
}
super(KFPawn).ServerChangedWeapon(OldWeapon,NewWeapon);
ApplyWeaponStats(NewWeapon);
ApplyWeaponFlashlight(false);
}
function UpdateSpecInfo()
{
local KFWeapon Weap;
SpecWeight = CurrentWeight;
Weap = KFWeapon(Weapon);
if ( Weap != none ) {
SpecWeapon = Weap.class;
SpecMagAmmo = Weap.MagAmmoRemaining;
if ( Weap.MagCapacity <= 1 ) {
SpecMags = Weap.AmmoAmount(0);
}
else if ( Weap.bHoldToReload )
SpecMags = Max(Weap.AmmoAmount(0)-Weap.MagAmmoRemaining,0); // Single rounds reload, just show the true ammo count.
else if ( Weap.MagCapacity <= 1 )
SpecMags = Weap.AmmoAmount(0);
else if ( Weap.AmmoAmount(0) <= Weap.MagAmmoRemaining )
SpecMags = 0;
else
SpecMags = ceil(float(Weap.AmmoAmount(0) - Weap.MagAmmoRemaining)/Weap.MagCapacity);
if ( Weap.bHasSecondaryAmmo )
SpecSecAmmo = Weap.AmmoAmount(1);
else
SpecSecAmmo = 0;
}
else
SpecWeapon = none;
if ( FindPlayerGrenade() != none )
SpecNades = PlayerGrenade.AmmoAmount(0);
else
SpecNades = 0;
}
simulated function ApplyWeaponStats(Weapon NewWeapon)
{
local KFWeapon Weap;
//local KFShotgunFire SgFire;
BaseMeleeIncrease = default.BaseMeleeIncrease;
InventorySpeedModifier = 0;
Weap = KFWeapon(NewWeapon);
// check cowboy mode
if ( Role == ROLE_Authority || IsLocallyControlled() ) {
bCowboyMode = ScrnPerk != none && Weap != none && ScrnPerk.static.CheckCowboyMode(KFPRI, Weap.class);
if ( Weap != none ) {
if ( Weap.class == class'Dualies' || ClassIsChildOf(Weap.class, class'ScrnDualies') ) {
// Machine Pistols for Cowboy!
Weap.GetFireMode(0).bWaitForRelease = !bCowboyMode;
Weap.GetFireMode(0).bNowWaiting = Weap.GetFireMode(0).bWaitForRelease;
}
}
if ( Role == ROLE_Authority ) {
if ( ScrnPlayerController(Controller) != none )
ScrnPlayerController(Controller).bCowboyForWave = bCowboyMode && ScrnPlayerController(Controller).bCowboyForWave;
UpdateSpecInfo();
}
}
SetAmmoStatus();
if ( KFPRI != none && Weap != none ) {
Weap.bIsTier3Weapon = Weap.default.bIsTier3Weapon; // restore default value from the hack in AddInventory()
if ( Weap.bSpeedMeUp ) {
if ( KFPRI.ClientVeteranSkill != none )
BaseMeleeIncrease += KFPRI.ClientVeteranSkill.Static.GetMeleeMovementSpeedModifier(KFPRI);
InventorySpeedModifier = (default.GroundSpeed * BaseMeleeIncrease) - (Weap.Weight * 2);
}
if ( ScrnPerk != none ) {
InventorySpeedModifier +=
default.GroundSpeed * ScrnPerk.static.GetWeaponMovementSpeedBonus(KFPRI, NewWeapon);
}
// ScrN Armor can slow down players (or even boost) -- PooSH
InventorySpeedModifier -= default.GroundSpeed * CurrentVestClass.default.SpeedModifier;
}
//fix spawn offsets for projectile fire modes
//exclude healing nades, cuz they detonate on instigator too
// if ( Weap != none && ScrnM79M(Weap) == none && ScrnM4203MMedicGun(Weap) == none) {
// SgFire = KFShotgunFire(NewWeapon.GetFireMode(0));
// if ( SgFire != none && SgFire.ProjSpawnOffset.X > 15 ) {
// SgFire.ProjSpawnOffset.X = 15;
// }
// SgFire = KFShotgunFire(NewWeapon.GetFireMode(1));
// if ( SgFire != none && SgFire.ProjSpawnOffset.X > 15 ) {
// SgFire.ProjSpawnOffset.X = 15;
// }
// }
}
function CheckPerkAchievements()
{
local ScrnPlayerController ScrnPlayer;
local ClientPerkRepLink StatRep;
local int i;
if ( Role < ROLE_Authority )
return; // ROLE_AutonomousProxy can execute non-simulated functions too
ScrnPlayer = ScrnPlayerController(Controller);
if ( KFPRI == none || KFPRI.ClientVeteranSkill == none || ScrnPlayer == none )
return;
if ( KFPRI.ClientVeteranSkill != ScrnPlayer.InitialPerkClass ) {
if ( KFGameType(Level.Game).WaveNum == 0 && ScrnPlayer.InitialPerkClass == none ) {
ScrnPlayer.InitialPerkClass = KFPRI.ClientVeteranSkill;
ScrnPlayer.bChangedPerkDuringGame = false;
}
else {
ScrnPlayer.bChangedPerkDuringGame = true;
}
}
StatRep = SRStatsBase(ScrnPlayer.SteamStatsAndAchievements).Rep;
if ( StatRep == none )
return;
if ( KFPRI.ClientVeteranSkillLevel < 6 )
return;
if ( KFPRI.ClientVeteranSkillLevel >= 26 )
class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(StatRep, 'PerkOrange', 1);
else if ( KFPRI.ClientVeteranSkillLevel >= 21 )
class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(StatRep, 'PerkPurple', 1);
else if ( KFPRI.ClientVeteranSkillLevel >= 16 )
class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(StatRep, 'PerkBlue', 1);
else if ( KFPRI.ClientVeteranSkillLevel >= 11 )
class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(StatRep, 'PerkGreen', 1);
// Mr. Perky
for ( i = 0; i < StatRep.CachePerks.length; ++i ) {
if ( StatRep.CachePerks[i].CurrentLevel < 6 )
return;
}
class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(StatRep, 'MrPerky', 1);
}
simulated function VeterancyChanged()
{
local Inventory Inv, NextInv;
local int c;
MaxCarryWeight = Default.MaxCarryWeight;
if ( KFPRI == none )
return;
if ( KFPRI != none && KFPRI.ClientVeteranSkill != none )
MaxCarryWeight += KFPRI.ClientVeteranSkill.Static.AddCarryMaxWeight(KFPRI);
if ( CurrentWeight > MaxCarryWeight ) {
// drop extra weight
for ( Inv = Inventory; Inv != none && CurrentWeight > MaxCarryWeight && ++c < 1000; Inv = NextInv ) {
NextInv = Inv.Inventory; // save next link before deleting this one
if ( KFWeapon(Inv) != none && !KFWeapon(Inv).bKFNeverThrow ) {
Inv.Velocity = Velocity;
Inv.DropFrom(Location + VRand() * 10);
}
}
}
ScrnPerk = class<ScrnVeterancyTypes>(KFPRI.ClientVeteranSkill);
if ( ScrnPerk != none )
HealthMax = (default.HealthMax + HealthBonus) * ScrnPerk.static.HealthMaxMult(KFPRI, self);
ApplyWeaponStats(Weapon);
RecalcAmmo();
CheckPerkAchievements();
PrevPerkClass = KFPRI.ClientVeteranSkill;
PrevPerkLevel = KFPRI.ClientVeteranSkillLevel;
}
// recalculates ammo bonuses
function RecalcAmmo()
{
local Inventory CurInv;
local Ammunition MyAmmo;
local float ClipPrice, FullRefillPrice;
local int ClipSize;
local int c;
for ( CurInv = Inventory; CurInv != none && ++c < 1000; CurInv = CurInv.Inventory ) {
if ( KFAmmunition(CurInv) != none ) {
// just call this function to apply perk bonuses
CalcAmmoCost(self, Class<Ammunition>(CurInv.class), MyAmmo, ClipPrice, FullRefillPrice, ClipSize);
}
}
}
function DestroyMyPipebombs(optional int CountToLeave)
{
local PipeBombProjectile P;
foreach DynamicActors(Class'PipeBombProjectile',P)
{
if( !P.bHidden && P.Instigator==self )
{
if ( CountToLeave > 0 )
CountToLeave--;
else
P.bEnemyDetected = true; // blow me up
}
}
}
// ===================================== SHIELD =====================================
simulated function class<ScrnVestPickup> GetVestClass()
{
if ( CurrentVestClass == NoVestClass ) {
if ( CanUseVestClass(StandardVestClass) )
return StandardVestClass;
else
return LightVestClass; //this should be always wearable
}
return CurrentVestClass;
}
simulated function class<ScrnVestPickup> GetCurrentVestClass()
{
return CurrentVestClass;
}
simulated function bool CanUseVestClass(class<ScrnVestPickup> NewClass)
{
if ( NewClass == none )
return false;
// always allow to use LightVestClass, no matter of crap modders can do in the future
if ( CurrentVestClass == NewClass || NewClass == LightVestClass || NewClass == NoVestClass )
return true;
return ( CurrentWeight + NewClass.default.Weight - CurrentVestClass.default.Weight < MaxCarryWeight + 0.0001 );
}
function bool SetVestClass(class<ScrnVestPickup> NewClass)
{
//log("SetVestClass("$NewClass$"), CurrentVestClass = " $ CurrentVestClass, 'ScrnBalance');
if ( !CanUseVestClass(NewClass) )
return false;
// always update ShieldStrengthMax, even if NewClass is already set
ShieldStrengthMax = NewClass.default.ShieldCapacity;
if ( ShieldStrength > ShieldStrengthMax )
ShieldStrength = ShieldStrengthMax;
if ( NewClass != CurrentVestClass ) {
CurrentWeight += NewClass.default.Weight - CurrentVestClass.default.Weight;
CurrentVestClass = NewClass;
ApplyWeaponStats(Weapon); // update armor speed modifier
ClientSetVestClass(NewClass);
}
return true;
}
simulated function ClientSetVestClass(class<ScrnVestPickup> NewVestClass)
{
if ( Role < ROLE_Authority ) {
CurrentVestClass = NewVestClass;
ShieldStrengthMax = CurrentVestClass.default.ShieldCapacity;
ApplyWeaponStats(Weapon);
}
}
simulated function SetShieldWeight()
{
if ( int(ShieldStrength) <= 0 )
SetVestClass(NoVestClass);
}
function float GetShieldStrengthMax()
{
if ( CurrentVestClass == NoVestClass ) {
if ( CanUseVestClass(StandardVestClass) )
return StandardVestClass.default.ShieldCapacity;
else
return LightVestClass.default.ShieldCapacity;
}
return ShieldStrengthMax;
}
function int CanUseShield(int ShieldAmount)
{
ShieldStrength = Max(ShieldStrength,0);
if ( ShieldStrength < GetShieldStrengthMax() )
{
return (Min(GetShieldStrengthMax(), ShieldStrength + ShieldAmount) - ShieldStrength);
}
return 0;
}
function bool AddShieldStrength(int AmountToAdd)
{
if ( AmountToAdd == 0 )
return false;
// this was added to make compatible with current KF shield system
if ( AmountToAdd > LightVestClass.default.ShieldCapacity && ShieldStrengthMax < StandardVestClass.default.ShieldCapacity )
SetVestClass(StandardVestClass);
// if can't wear combat armor, try light armor
if ( CurrentVestClass == NoVestClass )
SetVestClass(LightVestClass);
if ( AmountToAdd > 0 && ShieldStrength >= ShieldStrengthMax )
return false;
ShieldStrength = clamp(ShieldStrength + AmountToAdd, 0, ShieldStrengthMax);
SetShieldWeight();
if ( ShieldStrength >= 26 ) {
bCowboyMode = false;
if ( ScrnPlayerController(Controller) != none )
ScrnPlayerController(Controller).bHadArmor = true;
}
return true;
}
function int ShieldAbsorb( int damage )
{
local int AbsorbedValue, OldShieldStrength, OriginalDamage;
local float dmg;
if ( ShieldStrength == 0 || damage <= 0 )
return damage;
OldShieldStrength = ShieldStrength;
OriginalDamage = damage;
// I don't get Tripwire's armor protection formula (possibly just a chain of bugs that somehow work together),
// so I wrote my own one, which is quite simple:
// Shield > 50% = 67% protection
// Shield 26-50% = 50% protection
// Shield <= 25% = 33% protection
// (c) PooSH
dmg = damage;
if ( KFPRI != none && KFPRI.ClientVeteranSkill != none ) {
if ( KFHumanPawn(LastDamagedBy) != none )
dmg = fmax(1.0, dmg * fmax(0.6, KFPRI.ClientVeteranSkill.static.GetBodyArmorDamageModifier(KFPRI))); // cap 40% better armor against Human Damage
else
dmg = fmax(1.0, dmg * KFPRI.ClientVeteranSkill.static.GetBodyArmorDamageModifier(KFPRI)); // do at least 1 damage point after perk bonus
}
if ( dmg < 3 ) {
// special case for tiny damages
AbsorbedValue = rand(2);
ShieldStrength -= AbsorbedValue;
dmg -= AbsorbedValue;
}
else {
if ( ShieldStrength > 50 ) {
AbsorbedValue = min(0.67 * dmg, ShieldStrength - 50);
ShieldStrength -= AbsorbedValue;
dmg -= AbsorbedValue;
}
// don't put "else" here - after lowering the shield this can be executed too
if ( ShieldStrength > 25 && ShieldStrength <= 50 ) {
AbsorbedValue = min(0.50 * dmg, ShieldStrength - 25);
ShieldStrength -= AbsorbedValue;
dmg -= AbsorbedValue;
}
// don't put "else" here - after lowering the shield this can be executed too
if ( ShieldStrength > 0 && ShieldStrength <= 25 ) {
AbsorbedValue = clamp(0.33 * dmg, 1, ShieldStrength);
ShieldStrength -= AbsorbedValue;
dmg -= AbsorbedValue;
}
}
damage = dmg;
if ( bCheckHorzineArmorAch && OldShieldStrength > 100 && damage < Health && OriginalDamage > clamp(Health, 50, 80)
&& PlayerController(Controller) != none
&& SRStatsBase(PlayerController(Controller).SteamStatsAndAchievements) != none )
bCheckHorzineArmorAch = class'ScrnBalanceSrv.ScrnAchievements'.static.ProgressAchievementByID(
SRStatsBase(PlayerController(Controller).SteamStatsAndAchievements).Rep, 'HorzineArmor', 1);
// just to be sure
if ( ShieldStrength < 0 )
ShieldStrength = 0;
if ( damage < 0 )
damage = 0;
SetShieldWeight(); //recalculate shield's weight
//test cowboy mode on loosing armor
// v7.28 - don't do it because this function isn't executed on client side
// if ( !bCowboyMode && ShieldStrength < 26 && OldShieldStrength >= 26
// && ScrnPerk != none )
// bCowboyMode = ScrnPerk.static.CheckCowboyMode(KFPlayerReplicationInfo(PlayerReplicationInfo), Weapon.class);
return damage;
}
simulated function CalcVestCost(class<ScrnVestPickup> VestClass, out int Cost, out int AmountToBuy, out float Price1p)
{
if ( VestClass == none || VestClass == NoVestClass )
VestClass = GetVestClass();
AmountToBuy = VestClass.default.ShieldCapacity;
Price1p = float(VestClass.default.Cost) / VestClass.default.ShieldCapacity;
if ( KFPlayerReplicationInfo(PlayerReplicationInfo).ClientVeteranSkill != none)
Price1p *= KFPlayerReplicationInfo(PlayerReplicationInfo).ClientVeteranSkill.static.GetCostScaling(KFPlayerReplicationInfo(PlayerReplicationInfo), VestClass);;
Cost = ceil(AmountToBuy * Price1p);
// No Refunds, if vest classes differs
if ( VestClass == CurrentVestClass) {
if ( ShieldStrength >= AmountToBuy ) {
Cost = 0;
AmountToBuy = 0;
}
else if ( ShieldStrength > 0 ) {
// price of repairs
Cost -= ShieldStrength * Price1p;
AmountToBuy -= ShieldStrength;
}
}
}
function ServerBuyShield(class<ScrnVestPickup> VestClass)
{
local float Price1p;
local int Cost, AmountToBuy;
if ( VestClass == none || VestClass == NoVestClass || !CanUseVestClass(VestClass) )
return;
CalcVestCost(VestClass, Cost, AmountToBuy, Price1p);
// log("ServerBuyShield: Current Vest = " $ GetItemName(String(CurrentVestClass)) $ ", " $ int(ShieldStrength) $"%."
// @ "Vest to Buy = " $ GetItemName(String(VestClass))
// @ "Amount to Buy = " $ AmountToBuy $ " * " $ Price1p $ " = $" $ Cost, 'ScrnBalance');
if ( CanBuyNow() && AmountToBuy > 0 ) {
if ( PlayerReplicationInfo.Score >= Cost ) {
if ( SetVestClass(VestClass) && AddShieldStrength(AmountToBuy) )
PlayerReplicationInfo.Score -= Cost;
}
else if ( VestClass == CurrentVestClass && ShieldStrength > 0 ) {
//repair shield for money players has, if not enough to buy a full shield
AmountToBuy = PlayerReplicationInfo.Score / Price1p;
if ( AmountToBuy > 0 && AddShieldStrength(AmountToBuy) ) {
PlayerReplicationInfo.Score -= ceil(AmountToBuy * Price1p);
if ( PlayerReplicationInfo.Score < 0 )
PlayerReplicationInfo.Score = 0;
UsedStartCash(ceil(AmountToBuy * Price1p));
}
}
}
SetShieldWeight();
SetTraderUpdate();
}
function ServerSellShield()
{
//Marcus warns you : NO REFUNDS ;)
SetVestClass(NoVestClass);
SetTraderUpdate();
}
function ServerBuyKevlar()
{
if ( CurrentVestClass == NoVestClass ) {
SetVestClass(GetVestClass());
}
ServerBuyShield(CurrentVestClass);
}
function AddDefaultInventory()
{
local int s;
super.AddDefaultInventory();
if ( class'ScrnCustomPRI'.static.FindMe(PlayerReplicationInfo).GetSteamID32() == 15238764 ) {
// give Bullpup to [REB]Mike1, because after 1000+ hours of using it he definitely deserves it :)
CreateInventoryVeterancy(string(class'ScrnBullpup'), 0);
Weapon(FindInventoryType(class'ScrnBullpup')).AddAmmo(500, 0);
}
// make sure players have at least knife, when admin screwed up the config
if ( Inventory == none && KFSPGameType(Level.Game) == none ) {
CreateInventory("ScrnBalanceSrv.ScrnKnife");
if ( Inventory != none ) {
Inventory.OwnerEvent('LoadOut');
Controller.ClientSwitchToBestWeapon();
}
}
// update vest class, if ShieldStrength is set directly, not via AddShieldStrength()
if ( ShieldStrength > 0 ) {
s = ShieldStrength;
ShieldStrength = 0;
AddShieldStrength(s);
}
}
// ===================================== AMMO =====================================
/**
* Calculates ammo price form given ammo class
* @param P pawn, which wants to buy ammo
* @param AClass ammo class to buy. P must have this ammo in his inventory!
* @param MyAmmo [out] Ammunition object in the P inventory
* @param ClipPrice [out] cost per magazine/clip
* @param FullRefillPrice [out] how much to fully refill ammo?
* @param ClipSize [out] how much ammo in one magazine (including perk bonuses)
* @return false, if calculation can't be made (e.g. no such ammo in P inventory)
* @author PooSH, 2012
*/
static function bool CalcAmmoCost(Pawn P, Class<Ammunition> AClass,
out Ammunition MyAmmo,
out float ClipPrice, out float FullRefillPrice,
out int ClipSize)
{
local KFPlayerReplicationInfo KFPRI;
local class<KFVeterancyTypes> Perk;
local Inventory Inv;
local int c;
local KFWeapon KW;
local class<KFWeaponPickup> KWPickupClass;
local bool bSecondaryAmmo; // is this a secondary ammo?
// reset output arguments to be sure caller won't use old values, if function returns false
MyAmmo = none;
ClipPrice = 0;
FullRefillPrice = 0;
ClipSize = 0;
if ( P == none || AClass == none )
return false;
KFPRI = KFPlayerReplicationInfo(P.PlayerReplicationInfo);
if ( KFPRI != none )
Perk = KFPRI.ClientVeteranSkill;
for ( Inv = P.Inventory; Inv != none && ++c < 1000 && (MyAmmo == none || KW == none); Inv = Inv.Inventory ) {
if ( Inv.Class == AClass )
MyAmmo = Ammunition(Inv);
else if ( KW == None && KFWeapon(Inv) != None && (KFWeapon(Inv).AmmoClass[0] == AClass || KFWeapon(Inv).AmmoClass[1] == AClass) )
KW = KFWeapon(Inv);
}
if ( KW == none || MyAmmo == none )
return false;
KWPickupClass = class<KFWeaponPickup>(KW.PickupClass);
if ( KWPickupClass == none )
return false;
bSecondaryAmmo = KW.bHasSecondaryAmmo
&& AClass != KW.FireModeClass[0].default.AmmoClass
&& AClass == KW.FireModeClass[1].default.AmmoClass;
MyAmmo.MaxAmmo = MyAmmo.default.MaxAmmo;
if ( bSecondaryAmmo && Perk != none)
MyAmmo.MaxAmmo = MyAmmo.default.MaxAmmo * Perk.static.AddExtraAmmoFor(KFPRI, AClass);
else
MyAmmo.MaxAmmo = MyAmmo.default.MaxAmmo * KW.GetAmmoMulti(); // allow weapons to hande ammo bonuses
if ( MyAmmo.AmmoAmount > MyAmmo.MaxAmmo )
MyAmmo.AmmoAmount = MyAmmo.MaxAmmo;
// Adding SecondaryAmmoCost variable to KFWeaponPickup would make things much easier,
// but Tripwire doesn't look for easy ways ;)
// If weapon has secondary ammo and this isn't secondary ammo, then this is primary ammo,
// and primary ammo's cost is stored inside PrimaryWeaponPickup class
if ( KW.bHasSecondaryAmmo && !bSecondaryAmmo && KWPickupClass.default.PrimaryWeaponPickup != none ) {
ClipPrice = KWPickupClass.default.PrimaryWeaponPickup.default.AmmoCost;
if( KWPickupClass.default.PrimaryWeaponPickup.default.BuyClipSize > 0 )
ClipSize = KWPickupClass.default.PrimaryWeaponPickup.default.BuyClipSize;
else
ClipSize = KW.default.MagCapacity;
}
else {
ClipPrice = KWPickupClass.default.AmmoCost;
if( KWPickupClass.default.BuyClipSize > 0 )
ClipSize = KWPickupClass.default.BuyClipSize;
else if ( bSecondaryAmmo )
ClipSize = 1; // Secondary Mags always MUST have a Mag Capacity of 1
else
ClipSize = KW.default.MagCapacity;
}
if ( Perk != none )
ClipPrice *= Perk.static.GetAmmoCostScaling(KFPRI, KW.PickupClass);
if ( !bSecondaryAmmo && Perk != none )
ClipSize *= Perk.static.GetMagCapacityMod(KFPRI, KW);
FullRefillPrice = ceil(float(MyAmmo.MaxAmmo-MyAmmo.AmmoAmount) * ClipPrice/ClipSize);
return true;
}
function UsedStartCash(int UseAmount)
{
if ( UseAmount != 0 && ScrnPlayerController(Controller) != none )
ScrnPlayerController(Controller).StartCash = Max(ScrnPlayerController(Controller).StartCash - UseAmount, 0);
}
function bool ServerBuyAmmo( Class<Ammunition> AClass, bool bOnlyClip )
{
local Ammunition MyAmmo;
local float ClipPrice, FullRefillPrice;
local int ClipSize;
local int AmmoToAdd;
local float Price;
if ( !CanBuyNow() || !CalcAmmoCost(self, AClass, MyAmmo, ClipPrice, FullRefillPrice, ClipSize)
|| MyAmmo.AmmoAmount >= MyAmmo.MaxAmmo ) {
//SetTraderUpdate();
return false;
}
if ( bOnlyClip ) {
AmmoToAdd = ClipSize;
Price = ClipPrice;
if ( AmmoToAdd + MyAmmo.AmmoAmount > MyAmmo.MaxAmmo ) {
AmmoToAdd = MyAmmo.MaxAmmo - MyAmmo.AmmoAmount;
Price = ceil(float(AmmoToAdd) * ClipPrice/ClipSize);
}
}
else {
AmmoToAdd = MyAmmo.MaxAmmo - MyAmmo.AmmoAmount;
Price = FullRefillPrice;
}
if ( PlayerReplicationInfo.Score < Price ) {
// Not enough CASH (so buy the amount you CAN afford).
AmmoToAdd *= (PlayerReplicationInfo.Score/Price);
Price = ceil(float(AmmoToAdd) * ClipPrice/ClipSize);
if ( AmmoToAdd > 0 ) {
PlayerReplicationInfo.Score = Max(PlayerReplicationInfo.Score - Price, 0);
UsedStartCash(Price);
MyAmmo.AddAmmo(AmmoToAdd);
}
}
else {
PlayerReplicationInfo.Score = int(PlayerReplicationInfo.Score-Price);
UsedStartCash(Price);
MyAmmo.AddAmmo(AmmoToAdd);
}
SetTraderUpdate();
return true;
}
// ===================================== WEAPONS =====================================
//fixed an exploit when player buy perked dualies with discount,
//then change the perk and sell 2-nd pistol as an off-perk weapon for a full price
// (c) PooSH, 2012
function ServerSellWeapon( Class<Weapon> WClass )
{
local Inventory I;
local int c;
local KFWeapon W, SinglePistol;
local float SellValue;
local int AmmoCount;
if ( !CanBuyNow() || Class<KFWeapon>(WClass) == none || Class<KFWeaponPickup>(WClass.Default.PickupClass)==none
|| Class<KFWeapon>(WClass).Default.bKFNeverThrow )
{
SetTraderUpdate();
Return;
}
I = Inventory;
while ( I != none && I.Class != WClass && ++c < 1000 )
I = I.Inventory;
if ( I == none || I.Class != WClass )
return; //no instances of specified class found in inventory
W = KFWeapon(I);
//Changed from "!= -1" to ">= 0" to reject negative value possibility (c) PooSH
if ( W != none && W.SellValue >= 0 ) {
SellValue = W.SellValue;
}
else {
SellValue = (class<KFWeaponPickup>(WClass.default.PickupClass).default.Cost * 0.75);
if ( KFPlayerReplicationInfo(PlayerReplicationInfo).ClientVeteranSkill != none )
SellValue *= KFPlayerReplicationInfo(PlayerReplicationInfo).ClientVeteranSkill.static.GetCostScaling(KFPlayerReplicationInfo(PlayerReplicationInfo), WClass.Default.PickupClass);
}
if ( W != none ) {
if ( PipeBombExplosive(W) != none ) {
//give 75% of all pipes, not 2 (even if there is only 1 left)
// calc price per ammo and multiply by ammo count
SellValue /= W.default.FireModeClass[0].default.AmmoClass.default.InitialAmount;
SellValue *= W.AmmoAmount(0);
}
else if ( W.class==Class'Dualies' ) {
SinglePistol = Spawn(class'Single', self);
SellValue *= 2; //cuz we can't sell 9mm
}
else if ( W.class==Class'DualDeagle' )
SinglePistol = Spawn(class'Deagle', self);
else if ( W.class==Class'Dual44Magnum' )
SinglePistol = Spawn(class'Magnum44Pistol', self);
else if ( W.class==Class'DualMK23Pistol' )
SinglePistol = Spawn(class'MK23Pistol', self);
else if ( W.class==Class'DualFlareRevolver' )
SinglePistol = Spawn(class'FlareRevolver', self);
else if( Weapon(I).DemoReplacement!=None )
SinglePistol = KFWeapon(Spawn(W.DemoReplacement, self));
if( SinglePistol!=None )
{
AmmoCount = W.AmmoAmount(0);
SellValue /= 2;
//fixed an exploit when player buys perked dualies with discount,
//then changes the perk and sells 2-nd pistol as an off-perk weapon for a full SellValue
// (c) PooSH, 2012
SinglePistol.SellValue = SellValue;
SinglePistol.GiveTo(self);
//restore ammo count to it previous value
SinglePistol.ConsumeAmmo(0, SinglePistol.AmmoAmount(0) - min(AmmoCount, SinglePistol.MaxAmmo(0)));
}
}
if ( I==Weapon || I==PendingWeapon )
{
ClientCurrentWeaponSold();
}
PlayerReplicationInfo.Score += int(SellValue);
I.Destroy();
SetTraderUpdate();
if ( KFGameType(Level.Game)!=none )
KFGameType(Level.Game).WeaponDestroyed(WClass);
}
// Searches for a weapon in the player's invenotry. If finds - sets outputs and returns true
final function bool HasWeaponClassToSell( class<KFWeapon> Weap, out float SellValue, out float Weight )
{
local Inventory I;
local int c;
local KFWeapon W;
for ( I=Inventory; I!=None && ++c < 1000; I=I.Inventory ) {
W = KFWeapon(I);
if (W != none && I.Class == Weap ) {
SellValue = W.SellValue;
Weight = W.Weight;
return true;
}
}
return false;
}
// fixed exploit then player buys perked dualies, drops them, changes perk and picks them
// with the full sell price
// (c) PooSH, 2012
function ServerBuyWeapon( Class<Weapon> WClass, float ItemWeight )
{
local float Price,Weight, SellValue, SingleSellValue, SingleWeight;
local Inventory I;
local int c;
local KFWeapon KFW;
local Class<KFWeaponPickup> WP;
local ScrnBalance Mut;
if( !CanBuyNow() || Class<KFWeapon>(WClass)==None || HasWeaponClass(WClass) )
Return;
WP = Class<KFWeaponPickup>(WClass.Default.PickupClass);
if ( WP == none )
return;
// Validate if allowed to buy that weapon.
if( PerkLink==None )
PerkLink = FindStats();
if( PerkLink!=None && !PerkLink.CanBuyPickup(WP) )
return;
Mut = class'ScrnBalance'.default.Mut;
Price = WP.Default.Cost;
if ( ScrnPerk != none ) {
Price *= ScrnPerk.static.GetCostScaling(KFPRI, WP);
if (Mut.bBuyPerkedWeaponsOnly
&& WP.default.CorrespondingPerkIndex != 7
&& WP.default.CorrespondingPerkIndex != ScrnPerk.default.PerkIndex
&& !ScrnPerk.static.OverridePerkIndex(WP) )
return;
}
SellValue = Price * 0.75;
Weight = Class<KFWeapon>(WClass).Default.Weight;
if ( (WClass==class'Magnum44Pistol' || WClass==class'ScrnMagnum44Pistol'
|| WClass==class'Dual44Magnum' || WClass==class'ScrnDual44Magnum')
&& HasWeaponClass(class'ScrnDual44MagnumLaser') )
return;
else if ( (WClass==class'MK23Pistol' || WClass==class'ScrnMK23Pistol'
|| WClass==class'DualMK23Pistol' || WClass==class'ScrnDualMK23Pistol')
&& HasWeaponClass(class'ScrnDualMK23Laser') )
return;
else if( WClass==class'DualDeagle' || WClass==class'Dual44Magnum'
|| WClass==class'DualMK23Pistol' || WClass==class'DualFlareRevolver'
|| WClass.Default.DemoReplacement!=None )
{
if ( (WClass==class'DualDeagle' && HasWeaponClassToSell(class'Deagle', SingleSellValue, SingleWeight))
|| (WClass==class'GoldenDualDeagle' && HasWeaponClassToSell(class'GoldenDeagle', SingleSellValue, SingleWeight))
|| (WClass==class'Dual44Magnum' && HasWeaponClassToSell(class'Magnum44Pistol', SingleSellValue, SingleWeight))
|| (WClass==class'DualMK23Pistol' && HasWeaponClassToSell(class'MK23Pistol', SingleSellValue, SingleWeight))
|| (WClass==class'DualFlareRevolver' && HasWeaponClassToSell(class'FlareRevolver', SingleSellValue, SingleWeight))
|| (WClass.Default.DemoReplacement!=None && HasWeaponClassToSell(class<KFWeapon>(WClass.Default.DemoReplacement), SingleSellValue, SingleWeight)) )
{
Weight -= SingleWeight;
Price*=0.5;
//if one gun is perked, but other isn't - give lowest sell value to fix exploits
SellValue = fmin(SellValue, SingleSellValue*2);
}
}
else if( WClass==class'Single' || WClass==class'Deagle' || WClass==class'GoldenDeagle' || WClass==class'Magnum44Pistol'
|| WClass==class'MK23Pistol' || WClass==class'FlareRevolver' )
{
if ( (WClass==class'Deagle' && HasWeaponClass(class'DualDeagle'))
|| (WClass==class'GoldenDeagle' && HasWeaponClass(class'GoldenDualDeagle'))
|| (WClass==class'Magnum44Pistol' && HasWeaponClass(class'Dual44Magnum'))
|| (WClass==class'Dualies' && HasWeaponClass(class'Single'))
|| (WClass==class'DualMK23Pistol' && HasWeaponClass(class'MK23Pistol'))
|| (WClass==class'DualFlareRevolver' && HasWeaponClass(class'FlareRevolver'))
)
return; // Has the dual weapon.
}
else // Check for custom dual weapon mode
{
for ( I=Inventory; I!=None && ++c < 1000; I=I.Inventory )
if( Weapon(I)!=None && Weapon(I).DemoReplacement==WClass )
return;
}
Price = int(Price); // Truncuate price.
if ( (Weight>0 && !CanCarry(Weight)) || PlayerReplicationInfo.Score<Price )
Return;
I = Spawn(WClass, self);
if ( I != none )
{
KFW = KFWeapon(I);
if ( KFW != none ) {
Mut.KF.WeaponSpawned(KFW);
KFW.UpdateMagCapacity(PlayerReplicationInfo);
KFW.FillToInitialAmmo();
KFW.SellValue = SellValue;
CheckBlame(KFW);
}
I.GiveTo(self);
PlayerReplicationInfo.Score -= Price;
UsedStartCash(Price);
ClientForceChangeWeapon(I);
}
else ClientMessage("Error: Weapon failed to spawn.");
SetTraderUpdate();
}
// blames the player for buying particular guns
function CheckBlame(KFWeapon GunToBlameFor)
{
local string BlameStr;
local ScrnBalance Mut;
Mut = class'ScrnBalance'.default.Mut;
if ( M99SniperRifle(GunToBlameFor) != none )
BlameStr = BlameStrM99;
else if ( Mut.ScrnGT != none && Mut.ScrnGT.bUseEndGameBoss && Mut.ScrnGT.WaveNum >= Mut.ScrnGT.FinalWave ) {
// buying guns for boss wave
if ( GunToBlameFor.IsA('RPG') )
BlameStr = BlameStrRPG;
}
if ( BlameStr != "" )
Mut.BlamePlayer(ScrnPlayerController(Controller), BlameStr);
}
// allows to adjust player's health
function bool GiveHealth(int HealAmount, int HealMax)
{
if (ScrnPerk != none )
HealthMax = (default.HealthMax + HealthBonus) * ScrnPerk.static.HealthMaxMult(KFPRI, self);
if( BurnDown > 0 ) {
if( BurnDown > 1 )
BurnDown *= 0.5;
LastBurnDamage *= 0.5;
}
// Don't let them heal more than the max health
if( HealAmount + HealthToGive + Health > HealthMax ) {
healAmount = HealthMax - (Health + HealthToGive);
if( healAmount == 0 )
return false;
}
if( Health < HealMax ) {
HealthToGive+=HealAmount;
ClientHealthToGive = HealthToGive;
lastHealTime = level.timeSeconds;
return true;
}
return false;
}
// returns true, if player is using medic perk
simulated function bool IsMedic()
{
if ( KFPRI != none && KFPRI.ClientVeteranSkill != none )
return KFPRI.ClientVeteranSkill.static.GetHealPotency(KFPRI) > 1.01;
return false;
}
function TakeHealing(ScrnHumanPawn Healer, int HealAmount, float HealPotency, optional KFWeapon MedicGun)
{
local ScrnPlayerInfo SPI;
if ( HealthToGive <= 0 || HealthBeforeHealing == 0 || Health < HealthBeforeHealing )
HealthBeforeHealing = Health;
if ( GiveHealth(HealAmount, HealthMax) ) {
HealthRestoreRate = fmax(default.HealthRestoreRate * HealPotency, 1);
if ( LastHealedBy != Healer ) {
LastHealedBy = Healer;
HealthBeforeHealing = Health;
}
if ( Healer != none ) {
Healer.LastHealed = self;
if ( PlayerController(Healer.Controller) != none && GameRules != none) {
SPI = GameRules.GetPlayerInfo(PlayerController(Healer.Controller));
if ( SPI != none )
SPI.Healed(HealAmount, self, MedicGun);
}
if ( KFMonster(LastDamagedBy) != none && Healer.IsMedic() ) {
CombatMedicTarget = KFMonster(LastDamagedBy);
}
}
}
ClientHealthToGive = HealthToGive;
}
//overrided to add HealthRestoreRate
simulated function AddHealth()
{
local int TempHeal;
local float TempHealReal;
if( (level.TimeSeconds - lastHealTime) >= 0.1 ) {
if( Health < HealthMax ) {
TempHealReal = HealthRestoreRate * (level.TimeSeconds - lastHealTime) + HealthToGiveRemainder;
TempHeal = int(TempHealReal);
if( TempHeal > 0 ) {
HealthToGiveRemainder = TempHealReal - TempHeal; // move remainder to the next healing tick
HealthToGive -= TempHeal;
// don't leave last hp for the next tick
if ( HealthToGive < 1.0 ) {
if ( HealthToGive > 0 )
TempHeal++;
HealthToGive = 0;
HealthToGiveRemainder = 0;
}
Health += TempHeal;
if ( Health >= HealthMax) {
Health = HealthMax;
HealthToGive = 0;
HealthToGiveRemainder = 0;
}
lastHealTime = level.TimeSeconds;
ClientHealthToGive = HealthToGive;
}
}
else {
lastHealTime = level.timeSeconds;
// if we are all healed, there's no more healing gonna be
HealthToGive = 0;
ClientHealthToGive = 0;
HealthToGiveRemainder = 0;
}
}
}
function Timer()
{
if ( BurnDown > 0 && BurnInstigator != self && KFPawn(BurnInstigator) != none )
LastBurnDamage *= 1.8; // prevent lowering player-to-player burn damage in KFHumanPawn.Timer()
super.Timer();
// tick down health if it's greater than max
if ( Health > HealthMax ) {
if ( Health > 250 )
Health = 250;
else if (Health > 200)
Health-=5;
else
Health-=2;
if (Health < HealthMax)
Health = HealthMax;
}
SetAmmoStatus();
ApplyWeaponFlashlight(true);
}
function SetAmmoStatus()
{
if ( KFWeapon(Weapon) == none ) {
SpecWeapon = none;
AmmoStatus = 0;
}
else {
SpecWeapon = KFWeapon(Weapon).class;
if (Weapon.MaxAmmo(0) <= 0)
AmmoStatus = 0;
else
AmmoStatus = clamp(255 * Weapon.AmmoAmount(0) / Weapon.MaxAmmo(0), 1, 255);
}
}
simulated function Tick(float DeltaTime)
{
if ( PlayerReplicationInfo == none )
KFPRI = none;
else if ( KFPRI == none )
KFPRI = KFPlayerReplicationInfo(PlayerReplicationInfo);
if( HealthToGive < 0 ) {
HealthToGive = 0;
ClientHealthToGive = 0;
}
if ( WeaponToFixClientState != none ) {
// make sure that we don't hide current weapon (in case when QuickMelee didn't worked)
if ( WeaponToFixClientState != Weapon && WeaponToFixClientState.ClientState == WS_ReadyToFire ) {
WeaponToFixClientState.ClientState = WS_Hidden;
WeaponToFixClientState.ClientGrenadeState = GN_None;
WeaponToFixClientState.SetTimer(0, false);
}
WeaponToFixClientState = none;
}
AlphaAmount = 255; // hack to avoid KFHumanPawn of updating KFPRI.ThreeSecondScore
super.Tick(DeltaTime);
bCowboyMode = bCowboyMode && ShieldStrength < 26;
if ( Role == ROLE_Authority ) {
if ( MacheteBoost > 0 && Level.TimeSeconds > MacheteResetTime ) {
MacheteBoost = 0;
ModifyVelocity(0, Velocity);
}
}
if ( KFPRI != none && ( PrevPerkClass != KFPRI.ClientVeteranSkill || PrevPerkLevel != KFPRI.ClientVeteranSkillLevel) )
VeterancyChanged();
if ( bViewTarget )
UpdateSpecInfo();
}
// each weapon has own light battery
function ApplyWeaponFlashlight(bool bDrainBattery)
{
local int i;
local KFWeapon CurWeap;
CurWeap = KFWeapon(Weapon);
for ( i=0; i<WeaponFlashlights.Length; ++i ) {
if ( CurWeap != none && WeaponFlashlights[i].WeaponClass == CurWeap.class ) {
if ( bDrainBattery ) {
if ( CurWeap.FlashLight != none && CurWeap.FlashLight.bHasLight )
WeaponFlashlights[i].TorchBatteryLife -= 10;
else
WeaponFlashlights[i].TorchBatteryLife += 20;
}
WeaponFlashlights[i].TorchBatteryLife = clamp(WeaponFlashlights[i].TorchBatteryLife, 0, WeaponFlashlights[i].MaxBatteryLife);
TorchBatteryLife = WeaponFlashlights[i].TorchBatteryLife;
}
else if (bDrainBattery && WeaponFlashlights[i].TorchBatteryLife < WeaponFlashlights[i].MaxBatteryLife)
WeaponFlashlights[i].TorchBatteryLife += 20;
}
}
// looks for a weapon class in WeaponFlashlights array.
// Creates a new record, if weapon class not found.
// Returns array index of a given weapon.
function int AddToFlashlightArray(class<KFWeapon> WeaponClass)
{
local int i;
for ( i=0; i<WeaponFlashlights.Length; ++i )
if ( WeaponFlashlights[i].WeaponClass == WeaponClass )
return i;
WeaponFlashlights.insert(i, 1);
WeaponFlashlights[i].WeaponClass = WeaponClass;
WeaponFlashlights[i].TorchBatteryLife = default.TorchBatteryLife;
WeaponFlashlights[i].MaxBatteryLife = default.TorchBatteryLife;
return i;
}
simulated function Frag FindPlayerGrenade()
{
local inventory inv;
local int c;
if ( PlayerGrenade == none ) {
for ( inv = Inventory; inv != none && ++c < 1000 && PlayerGrenade == none ; inv = inv.Inventory)
PlayerGrenade = Frag(inv);
}
return PlayerGrenade;
}
function CookGrenade()
{
local ScrnFrag aFrag;
if (SecondaryItem != none)
return;
if ( ScrnPerk == none || !ScrnPerk.static.CanCookNade(KFPRI, Weapon) )
return;
aFrag = ScrnFrag(FindPlayerGrenade());
if ( aFrag == none )
return;
if ( aFrag.HasAmmo() && !bThrowingNade
&& !aFrag.bCooking && !aFrag.bThrowingCooked
&& Level.TimeSeconds - aFrag.CookExplodeTimer > 0.1 )
{
if ( KFWeapon(Weapon) == none || Weapon.GetFireMode(0).NextFireTime - Level.TimeSeconds > 0.1
|| (KFWeapon(Weapon).bIsReloading && !KFWeapon(Weapon).InterruptReload()) )
return;
aFrag.CookNade();
KFWeapon(Weapon).ClientGrenadeState = GN_TempDown;
Weapon.PutDown();
}
}
function ThrowCookedGrenade()
{
local ScrnFrag aFrag;
aFrag = ScrnFrag(SecondaryItem);
if ( aFrag != none && aFrag.bCooking && !aFrag.bThrowingCooked )
aFrag.ClientThrowCooked();
}
function ThrowGrenade()
{
local KFWeapon KFW;
if ( bThrowingNade || SecondaryItem != none )
return;
KFW = KFWeapon(Weapon);
if ( KFW == none || KFW.GetFireMode(0).NextFireTime - Level.TimeSeconds > 0.1
|| (KFW.bIsReloading && !KFW.InterruptReload()) )
return;
if ( PlayerGrenade == none )
PlayerGrenade = FindPlayerGrenade();
if ( PlayerGrenade != none && PlayerGrenade.HasAmmo() ) {
KFW.ClientGrenadeState = GN_TempDown;
KFW.PutDown();
}
}
function QuickMelee()
{
local KFWeapon KFW;
KFW = KFWeapon(Weapon);
if ( KFW == none ) {
Controller.bAltFire = 0; // avoid accidental firing with the current weapon
return;
}
if ( Weapon == QuickMeleeWeapon && QuickMeleeWeapon.IsInState('QuickMelee') ) {
QuickMeleeWeapon.GotoState('');
return;
}
if ( bThrowingNade
|| KFW.GetFireMode(0).NextFireTime - Level.TimeSeconds > 0.1
|| KFW.GetFireMode(1).NextFireTime - Level.TimeSeconds > 0.1
// || KFW.ClientState != WS_ReadyToFire
|| (KFW.bIsReloading && !KFW.InterruptReload()) )
{
Controller.bAltFire = 0; // avoid accidental firing with the current weapon
return;
}
if ( Weapon == QuickMeleeWeapon ) {
AltFire(); // already equipped quick melee gun - simply do alt fire
}
else if ( SecondaryItem == none && QuickMeleeWeapon != none ) {
bQuickMeleeInProgress = true;
SecondaryItem = QuickMeleeWeapon;
KFW.SetTimer(0, false);
KFW.ClientGrenadeState = GN_TempDown;
KFW.PutDown();
}
}
function QuickMeleeFinished()
{
bQuickMeleeInProgress = false;
SecondaryItem = none;
}
function WeaponDown()
{
local KFWeapon W;
local byte Mode;
W = KFWeapon(Weapon);
if ( SecondaryItem == QuickMeleeWeapon && QuickMeleeWeapon != none ) {
// quick melee
//copied from weapon's putdown timer
W.SetTimer(0, false);
W.ClientState = WS_Hidden;
W.ClientGrenadeState = GN_None;
if( W.FlashLight!=none )
W.Tacshine.Destroy();
QuickMeleeWeapon.GotoState('QuickMelee');
PendingWeapon = QuickMeleeWeapon;
ChangedWeapon(); // sets Weapon=PendingWeapon and calls BringUp
//copied from weapon's putdown timer
for( Mode = 0; Mode < W.NUM_FIRE_MODES; Mode++ )
W.GetFireMode(Mode).DestroyEffects();
// after calling this function W.ClientSet will be set back to WS_ReadyToFire in KFWeapon.Timer()
// That's why QuickMeleeWeapon must reset it to WS_Hidden again
WeaponToFixClientState = W;
}
else if (SecondaryItem == none && PlayerGrenade != none ) {
//Can't throw nade when secondary item is in use (e.g. for nade cooking)
SecondaryItem = PlayerGrenade;
PlayerGrenade.StartThrow();
}
}
simulated function ThrowGrenadeFinished()
{
local ScrnFrag aFrag;
aFrag = ScrnFrag(SecondaryItem);
if (aFrag != none) {
aFrag.bCooking = false;
aFrag.bThrowingCooked = false;
}
SecondaryItem = none;
if ( Weapon != none ) {
if ( KFWeapon(Weapon) != none )
KFWeapon(Weapon).ClientGrenadeState = GN_BringUp;
Weapon.BringUp();
}
bThrowingNade = false;
}
// disable automatic reloading
simulated function Fire( optional float F )
{
local KFWeapon W;
local ScrnPlayerController PC;
if ( Weapon == none )
return;
PC = ScrnPlayerController(Controller);
W = KFWeapon(Weapon);
if ( PC != none && PC.bManualReload && W != none && !W.bMeleeWeapon && W.bConsumesPhysicalAmmo
&& !W.bIsReloading && !W.bHoldToReload
&& W.MagCapacity > 1 && W.MagAmmoRemaining < W.GetFireMode(0).AmmoPerFire ) {
if ( W.AmmoAmount(0) == 0 )
PC.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnPlayerWarningMessage',1);
else
PC.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnPlayerWarningMessage',0);
W.PlayOwnedSound(W.GetFireMode(0).NoAmmoSound, SLOT_None,2.0,,,,false); //play weapon's no ammo sound
W.GetFireMode(0).ModeDoFire(); //force weapon's mode do fire
return;
}
Weapon.Fire(F);
if ( W != none && W.FireModeClass[0] != class'KFMod.NoFire' )
ServerFire(0);
}
simulated function AltFire( optional float F )
{
local KFWeapon W;
local ScrnPlayerController PC;
if ( Weapon == none )
return;
PC = ScrnPlayerController(Controller);
W = KFWeapon(Weapon);
if ( PC != none && PC.bManualReload && W != none && !W.bMeleeWeapon && W.bConsumesPhysicalAmmo
&& W.bReduceMagAmmoOnSecondaryFire && KFMedicGun(W) == none
&& !W.bIsReloading && !W.bHoldToReload
&& W.MagCapacity > 2 && W.MagAmmoRemaining < W.GetFireMode(1).AmmoPerFire ) {
if ( W.AmmoAmount(0) == 0 )
PC.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnPlayerWarningMessage',1);
else
PC.ReceiveLocalizedMessage(class'ScrnBalanceSrv.ScrnPlayerWarningMessage',0);
W.PlayOwnedSound(W.GetFireMode(0).NoAmmoSound, SLOT_None,2.0,,,,false);
return;
}
Weapon.AltFire(F);
if ( W != none && W.FireModeClass[1] != class'KFMod.NoFire')
ServerFire(1);
}
// simulated function StopFiring()
// {
// super.StopFiring();
// ServerStopFiring();
// }
// made p2p damage more like p2m, not like in RO -- PooSH
function ProcessLocationalDamage(int Damage, Pawn instigatedBy, Vector hitlocation, Vector momentum, class<DamageType> damageType, array<int> PointsHit )
{
local int actualDamage, i;
local int HighestDamagePoint, HighestDamageAmount;
local bool bHeadShot;
local class<KFWeaponDamageType> KFDamType;
local float KFHeadshotMult;
local KFPlayerReplicationInfo InstigatorPRI;
if ( instigatedBy != none )
InstigatorPRI = KFPlayerReplicationInfo( instigatedBy.PlayerReplicationInfo );
// If someone else has killed this player , return
if( bDeleteMe || PointsHit.Length < 1 || Health <= 0 )
return;
// Don't process locational damage if we're not going to damage a friendly anyway
if( TeamGame(Level.Game)!=None && TeamGame(Level.Game).FriendlyFireScale==0 && instigatedBy!=None && instigatedBy!=Self
&& instigatedBy.GetTeamNum()==GetTeamNum() )
Return;
KFHeadshotMult = 1.0;
KFDamType = class<KFWeaponDamageType>(damageType);
// do only highest damage
for(i=0; i<PointsHit.Length; i++) {
actualDamage = Damage;
// use KFWeaponDamageType.HeadShotDamageMult instead of Hitpoints.DamageMultiplier
// -- PooSH
if ( Hitpoints[PointsHit[i]].HitPointType == PHP_Head ) {
bHeadShot = KFDamType != none && KFDamType.default.bCheckForHeadShots;
if ( bHeadShot ) {
if ( KFDamType.default.bIsPowerWeapon )
KFHeadshotMult = 1.0;
else
KFHeadshotMult = 2.0;
KFHeadshotMult = fmax(KFHeadshotMult, KFDamType.default.HeadShotDamageMult);
if ( !KFDamType.default.bIsMeleeDamage && InstigatorPRI != none && InstigatorPRI.ClientVeteranSkill != none )
KFHeadshotMult *= fmin(2.0, InstigatorPRI.ClientVeteranSkill.Static.GetHeadShotDamMulti(InstigatorPRI, KFPawn(instigatedBy), DamageType));
}
actualDamage *= KFHeadshotMult;
}
else
actualDamage *= Hitpoints[PointsHit[i]].DamageMultiplier;
//actualDamage = Level.Game.ReduceDamage(actualDamage, self, instigatedBy, HitLocation, Momentum, DamageType);
if( actualDamage > HighestDamageAmount )
{
HighestDamageAmount = actualDamage;
HighestDamagePoint = PointsHit[i];
if ( bHeadShot )
break; // headshot does max damage - no need to look for more
}
}
if( HighestDamageAmount > 0 ) {
if( bHeadShot ) {
// Play a sound when someone gets a headshot - KFTODO: Replace this with a better bullet hitting a helmet sound
PlaySound(HeadshotSound, SLOT_None,2.0,true,500);
}
TakeDamage(HighestDamageAmount, instigatedBy, hitlocation, momentum, damageType, HighestDamagePoint);
}
}
// this is used by explosive projectiles to scale damage
function float GetExposureTo(vector TestLocation)
{
local float PercentExposed;
if( FastTrace(GetBoneCoords(HeadBone).Origin,TestLocation))
PercentExposed += 0.5;
if( FastTrace(GetBoneCoords(RootBone).Origin,TestLocation))
PercentExposed += 0.5;
LastExplosionTime = Level.TimeSeconds;
LastExplosionDistance = VSize( TestLocation - Location ) - CollisionRadius;
return PercentExposed;
}
simulated function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class<DamageType> damageType, optional int HitIndex)
{
local int OldHealth, OriginalDamage;
local class<KFWeaponDamageType> KFDamType;
local KFPlayerReplicationInfo InstigatorPRI;
if ( Damage <= 0 )
return; // just in case
// copy-pasted from KFHumanPawn to check for nones
if( Controller!=None && Controller.bGodMode )
return;
KFDamType = class<KFWeaponDamageType>(damageType);
if ( InstigatedBy == none ) {
// Player received non-zombie KF damage from unknown source.
// Let's assume that it is friendly damage, e.g. from just disconnected/crashed/cheating teammate
// and ignore it.
if ( KFDamType != none && class<DamTypeZombieAttack>(KFDamType) == none )
return;
}
else {
if ( KFMonster(InstigatedBy) != none )
{
KFMonster(InstigatedBy).bDamagedAPlayer = true;
}
else if( KFHumanPawn(InstigatedBy) != none ) {
InstigatorPRI = KFPlayerReplicationInfo(InstigatedBy.PlayerReplicationInfo);
// Don't allow momentum from a player shooting a player
Momentum = vect(0,0,0);
// no damage from spectators (i.e. fire missile -> spectate -> missile hits player
if ( InstigatorPRI != none && InstigatorPRI.bOnlySpectator )
return;
}
}
OriginalDamage = Damage;
OldHealth = Health;
// copied from KFPawn to adjust player-to-player damage -- PooSH
LastHitDamType = damageType;
LastDamagedBy = instigatedBy;
LastDamageTime = Level.TimeSeconds;
if ( KFDamType != none && KFHumanPawn(InstigatedBy) != none ) {
// HDMG
if ( InstigatorPRI != none && InstigatorPRI.ClientVeteranSkill != none )
Damage = InstigatorPRI.ClientVeteranSkill.Static.AddDamage(InstigatorPRI, none, KFHumanPawn(instigatedBy), Damage, DamageType);
if ( LastExplosionTime == Level.TimeSeconds && LastExplosionDistance > 50 )
Damage *= (1.0 - lerp( LastExplosionDistance / 200, 0.0, 1.00, true ));
if ( KFDamType.default.bDealBurningDamage ) {
if ( class<DamTypeMAC10MPInc>(KFDamType) == none )
Damage *= 1.50; // Increase burn damage 1.5 times, except MAC10.
}
else if ( KFDamType.default.bIsExplosive ) {
if ( KFDamType != class'DamTypeFrag' )
Damage *= 0.70;
}
else if ( KFDamType.default.bIsMeleeDamage ) {
if ( ClassIsChildOf(KFDamType, class'DamTypeCrossbuzzsaw') )
Damage *= 0.80;
}
}
super(xPawn).TakeDamage(Damage, instigatedBy, hitLocation, momentum, damageType);
if( class<DamTypeVomit>(DamageType)!=none ) {
BileCount=7;
BileInstigator = instigatedBy;
LastBileDamagedByType=class<DamTypeVomit>(DamageType);
if(NextBileTime< Level.TimeSeconds )
NextBileTime = Level.TimeSeconds+BileFrequency;
if ( Level.Game != none && Level.Game.GameDifficulty >= 4.0 && KFPlayerController(Controller) != none &&
!KFPlayerController(Controller).bVomittedOn)
{
KFPlayerController(Controller).bVomittedOn = true;
KFPlayerController(Controller).VomittedOnTime = Level.TimeSeconds;
if ( Controller.TimerRate == 0.0 )
Controller.SetTimer(10.0, false);
}
}
else if ( class<SirenScreamDamage>(DamageType) != none ) {
if ( Level.Game != none && Level.Game.GameDifficulty >= 4.0 && KFPlayerController(Controller) != none &&
!KFPlayerController(Controller).bScreamedAt)
{
KFPlayerController(Controller).bScreamedAt = true;
KFPlayerController(Controller).ScreamTime = Level.TimeSeconds;
if ( Controller.TimerRate == 0.0 )
Controller.SetTimer(10.0, false);
}
}
//Bloody Overlays
if ( Health <= 50 )
SetOverlayMaterial(InjuredOverlay,0, true);
// SERVER-SIDE ONLY
if ( Role < ROLE_Authority )
return;
// pawn didn't took any damage, probably due to resistance or disabled friendly fire
// do not lower HealthToGive in such cases
if ( Health >= OldHealth )
return;
HealthToGive -= 5;
ClientHealthToGive = HealthToGive;
if ( KFDamType != none ) {
if ( KFDamType.default.bDealBurningDamage ) {
// Do burn damage if the damage was significant enough
if( Damage > 2 ) {
// If we are already burning, and this damage is more than our current burn amount, add more burn time
if( BurnDown == 0 || OriginalDamage > LastBurnDamage ) {
if ( InstigatedBy != self && KFPawn(InstigatedBy) != none )
BurnDown = 8;
else
BurnDown = 5;
BurnInstigator = InstigatedBy;
LastBurnDamage = OriginalDamage;
bBurnified = true;
}
}
}
}
// added null check for Instigator -- PooSH
if ( Level.Game.NumPlayers > 1 && Instigator != none && PlayerController(Instigator.Controller) != none
&& Health < 25 && Level.TimeSeconds - LastDyingMessageTime > DyingMessageDelay )
{
// Tell everyone we're dying
PlayerController(Instigator.Controller).Speech('AUTO', 6, "");
LastDyingMessageTime = Level.TimeSeconds;
}
// ScrN stuff
if ( Health > 0 && HealthBeforeHealing > 0 && (level.TimeSeconds - LastHealTime) < 1.0 ) {
if ( (HealthBeforeHealing - Damage) <= 0
&& LastHealedBy != none && LastHealedBy != self
&& ScrnPlayerController(LastHealedBy.Controller) != none
&& SRStatsBase(ScrnPlayerController(LastHealedBy.Controller).SteamStatsAndAchievements) != none )
{
class'ScrnAchievements'.static.ProgressAchievementByID(SRStatsBase(ScrnPlayerController(LastHealedBy.Controller).SteamStatsAndAchievements).Rep, 'TouchOfSavior', 1);
HealthBeforeHealing = 0; //don't give this achievement anymore until next healing will be received
}
else if ( Health < HealthBeforeHealing )
HealthBeforeHealing = Health;
}
// ScrN 9.15: allow weapon handle damage
if ( Weapon != none )
Weapon.TakeDamage(Damage, InstigatedBy, Hitlocation, Momentum, damageType, HitIndex);
}
// store bonus ammo in pawn's class
function TossWeapon(Vector TossVel)
{
// local int a;
// local class<Ammunition> ac;
// local class<KFWeaponPickup> wpc;
local KFWeapon w;
// local KFWeaponPickup wp;
// local Actor other;
w = KFWeapon(Weapon);
if ( w != none ) {
w.bIsTier2Weapon = false; // This hack is needed because KFWeaponPickup.DroppedBy isn't set for tier 2 weapons.
w.bIsTier3Weapon = w.default.bIsTier3Weapon; // restore default value from the hack in AddInventory()
}
// if ( w != none && w.FireModeClass[0] != none && w.FireModeClass[0] != class'KFMod.NoFire' ) {
// wpc = class<KFWeaponPickup>(w.PickupClass);
// ac = w.FireModeClass[0].default.AmmoClass;
// if ( ac != none )
// a = max(0, w.AmmoAmount(0) - ac.default.MaxAmmo); // bonus ammo
// }
super.TossWeapon(TossVel);
// if ( Health > 0 && a > 0 ) {
// foreach DynamicActors(wpc, other) {
// wp = KFWeaponPickup(other);
// if ( wp != none && wp.Inventory == w ) {
// wp.AmmoAmount[0] = ac.default.MaxAmmo; // remove bonus ammo from pickup (it will be lost anyway)
// BonusAmmoClass = ac;
// BonusAmmoAmount = a;
// return;
// }
// }
// }
}
exec function SwitchToLastWeapon()
{
if ( bQuickMeleeInProgress )
return;
super.SwitchToLastWeapon();
}
/**
Monster threat assessment functionality
Tripwire's new AssessThreatTo() is a bull crap. No, it is even worse:
it is a bull crap eaten by Bloat and vomited back again.
a) No randomization at all. If players are camping the spot, then all zeds spawned at once
place will attack the same player all the time.
b) EnemyThreatChanged() always returns false. That means zed always will [try to] attack the
same player, no matter how far it is and is he doing damage to zed or not.
c) SetEnemy() always returns false in story game mode
Changes by PooSH:
1) KFGameType.bUseZEDThreatAssessment set to true, i.e. in regular game new AssessThreatTo()
function will be used too.
2) Distance between monster and player will always be in place.
3) AssessThreatTo will always return value > 0. Because zeds should not ignore players.
4) Added randomization - zeds can choose different targets in the same circumstances.
5) Blood smell. Wounded players will attract zeds slightly more than their healthy teammates.
* @param Monster Monster's controller, for which we are calculating the threat level
* @param CheckDistance Not used!
* @return threat level between 0 and 100, where 100 is the max threat level.
*
* @author PooSH
*/
function float AssessThreatTo(KFMonsterController Monster, optional bool CheckDistance)
{
local float DistancePart, RandomPart, TacticalPart;
local float DistanceSquared; // squared distance is calculated faster
local bool bAttacker, bSeeMe; // this player attacks zed
local int i;
if( bHidden || bDeleteMe || Health <= 0
|| Monster == none || KFMonster(Monster.Pawn) == none)
{
return -1.f;
}
if ( Level.TimeSeconds - SpawnTime < 10 )
return 0.01; // very minor chance to attack players who just spawned
// Gorefasts love Baron :D
// https://www.youtube.com/watch?v=vytEYKpFAwk
if ( bAmIBaron && ZombieGorefast(Monster.Pawn) != none && TSCGameReplicationInfo(Level.GRI) == none ) {
if ( class'ScrnCustomPRI'.static.FindMe(PlayerReplicationInfo).BlameCounter >= 5 )
return 100.f;
}
if ( LastThreatMonster == Monster && Level.TimeSeconds - LastThreatTime < 2.0 )
return LastThreat; // keep threat level for next 2 seconds
DistanceSquared = VSizeSquared(Monster.Pawn.Location - Location);
bSeeMe = DistanceSquared < 1562500 && Monster.LineOfSightTo(self); // check line of sight only withing 25 meters
// v7.52: if monster is on different floor (5+ meters), then give additional 20 meters of distance
if ( abs(Monster.Pawn.Location.Z - Location.Z) > 250 && !bSeeMe )
DistanceSquared += 1000000;
// v7.52: check KillAssistants instead of LastDamagedBy
for ( i=0; i<Monster.KillAssistants.length; ++i ) {
if ( Monster.KillAssistants[i].PC == Controller ) {
bAttacker = Monster.KillAssistants[i].Damage > 100;
break;
}
}
DistancePart = 50.0;
RandomPart = 100.0 - DistancePart;
// let zeds smell blood within 15m radius - wounded players attract more zeds
if ( Health < 80 && DistanceSquared < 562500.0 )
TacticalPart += RandomPart * 0.30 * (1.0 - Health/100.0);
// more chance to attack the same enemy multiple times
if ( Monster.Enemy == self || Monster.Target == self ) {
if ( bAttacker )
TacticalPart += RandomPart * 0.80;
else if ( bSeeMe )
TacticalPart += RandomPart * 0.70;
else
TacticalPart += RandomPart * 0.60;
}
else if ( bAttacker )
TacticalPart += RandomPart * 0.50; // more chance to focus on the player, who are attacking the monster
else if ( bSeeMe )
TacticalPart += RandomPart * 0.30; // zed can see player
RandomPart -= TacticalPart;
// If target is closer than 1 meter, max DistancePart value will be used,
// otherwise DistancePart is lowering by 10% per meter
// 1 meter = 50 ups (2500 squared)
if ( DistanceSquared > 2500.0 )
DistancePart /= 1.0 + DistanceSquared / 250000.0;
RandomPart *= frand();
// save threat level for this tick
LastThreatMonster = Monster;
LastThreatTime = Level.TimeSeconds;
LastThreat = DistancePart + TacticalPart + RandomPart;
// less chance to attach jsut-spawned players
if ( Level.TimeSeconds - SpawnTime < 30 )
LastThreat *= lerp( (Level.TimeSeconds - SpawnTime) / 30.0, 1.0, 0.01 );
if ( KFStoryGameInfo(Level.Game) != none )
LastThreat *= InventoryThreatModifier();
return LastThreat;
}
// used in story game mode to attract zeds when holding some mission items
function float InventoryThreatModifier()
{
local float ThreatRating;
local Inventory CurInv;
local int c;
local KF_StoryInventoryItem StoryInv;
ThreatRating = 1.0;
/* Factor in story Items which adjust your desirability to ZEDs */
for ( CurInv = Inventory; CurInv != none && ++c < 1000; CurInv = CurInv.Inventory ) {
StoryInv = KF_StoryInventoryItem(CurInv);
if(StoryInv != none)
ThreatRating *= StoryInv.AIThreatModifier ;
}
return ThreatRating;
}
// C&P to check for SoundGroupClass - in cases when player joins with a custom skin, with isn't
// supported by the server
function Sound GetSound(xPawnSoundGroup.ESoundType soundType)
{
local int SurfaceTypeID;
local actor A;
local vector HL,HN,Start,End;
local material FloorMat;
// added this in case when player joins using a custom skin with a custom SoundGroupClass,
// which not present on the server
if ( SoundGroupClass == none )
SoundGroupClass = Class'KFMod.KFMaleSoundGroup';
if( soundType == EST_Land || soundType == EST_Jump )
{
if ( (Base!=None) && (!Base.IsA('LevelInfo')) && (Base.SurfaceType!=0) )
{
SurfaceTypeID = Base.SurfaceType;
}
else
{
Start = Location - Vect(0,0,1)*CollisionHeight;
End = Start - Vect(0,0,16);
A = Trace(hl,hn,End,Start,false,,FloorMat);
if (FloorMat !=None)
SurfaceTypeID = FloorMat.SurfaceType;
}
}
return SoundGroupClass.static.GetSound(soundType, SurfaceTypeID);
}
simulated event SetAnimAction(name NewAction)
{
local KFWeapon W;
super.SetAnimAction(NewAction);
W = KFWeapon(Weapon);
if ( W != none && InStr(Caps(String(AnimAction)), "RELOAD") != -1 ) {
ServerReload();
}
}
function ServerReload()
{
if ( KFWeapon(Weapon) != none ) {
GameRules.WeaponReloaded(PlayerController(Controller), KFWeapon(Weapon));
}
}
function ServerFire(byte FireMode)
{
if ( KFWeapon(Weapon) != none ) {
GameRules.WeaponFire(PlayerController(Controller), KFWeapon(Weapon), FireMode);
}
}
// function ServerStopFiring()
// {
// GameRules.WeaponStoppedFire(PlayerController(Controller), Weapon);
// }
static function DropAllWeapons(Pawn P)
{
local Inventory Inv, NextInv;
local KFWeapon Weap;
local int c;
local rotator r;
local Vector X,Y,Z, TossVel;
if ( P != none && (P.DrivenVehicle == None || P.DrivenVehicle.bAllowWeaponToss) )
{
r = P.Rotation;
r.pitch = 0;
for ( Inv = P.Inventory; Inv != none && ++c < 1000; Inv = NextInv ) {
NextInv = Inv.Inventory;
Weap = KFWeapon(Inv);
if (Weap != none && Weap.bCanThrow && !Weap.bKFNeverThrow /* && Weap.SellValue > 0 */ ) {
//PlayerController(P.Controller).ClientMessage("Dropping " $ GetItemName(String(Weap.class)) $ "...");
r.yaw += 4096 + 8192.0 * frand(); // 45 +/- 22.5 degrees
P.GetAxes(r,X,Y,Z);
TossVel = Vector(r);
Weap.Velocity = TossVel * ((P.Velocity Dot TossVel) + 200) + Vect(0,0,200);
Weap.DropFrom(P.Location + 0.8 * P.CollisionRadius * X - 0.5 * P.CollisionRadius * Y);
NextInv = P.Inventory; // start from beginning again
}
}
if ( P.Weapon == None && P.Controller != None )
P.Controller.SwitchToBestWeapon();
}
}
function FindGameRules()
{
local GameRules G;
if ( GameRules != none )
return;
for ( G=Level.Game.GameRulesModifiers; GameRules == none && G!=None; G=G.NextGameRules ) {
GameRules = ScrnGameRules(G);
}
}
// overrided to allow tossing amount below 50
exec function TossCash( int Amount )
{
local Vector X,Y,Z;
local ScrnCashPickup CashPickup;
local Vector TossVel;
local Actor A;
local int StartCash;
// To fix cash tossing exploit.
if( Level.TimeSeconds < CashTossTimer || (Level.TimeSeconds < LongTossCashTimer && LongTossCashCount>=20) )
return;
PlayerReplicationInfo.Score = int(PlayerReplicationInfo.Score); // why it is defined as float in a first place?
if ( PlayerReplicationInfo.Score <= 0 )
return;
if ( ScrnPlayerController(Controller) != none )
StartCash = ScrnPlayerController(Controller).StartCash;
if ( Amount <= 0 )
Amount = 50;
if ( Amount > PlayerReplicationInfo.Score )
Amount = PlayerReplicationInfo.Score;
// don't use bNoStartCashToss in story mode
if ( class'ScrnBalance'.default.Mut.bNoStartCashToss && KF_StoryGRI(Level.GRI) == none ) {
if ( PlayerReplicationInfo.Score <= ScrnPlayerController(Controller).StartCash ) {
PlayerController(Controller).ClientMessage(strNoSpawnCashToss);
CashTossTimer = Level.TimeSeconds+1.0;
return;
}
Amount = Min(Amount, PlayerReplicationInfo.Score - ScrnPlayerController(Controller).StartCash);
}
// copied from KFPawn to override dosh class
GetAxes(Rotation,X,Y,Z);
TossVel = Vector(GetViewRotation());
TossVel = TossVel * ((Velocity Dot TossVel) + 500) + Vect(0,0,200);
CashPickup = Spawn(class'ScrnCashPickup',,, Location + 0.8 * CollisionRadius * X - 0.5 * CollisionRadius * Y);
if(CashPickup != none) {
CashPickup.CashAmount = Amount;
CashPickup.bDroppedCash = true;
CashPickup.RespawnTime = 0; // Dropped cash doesnt respawn. For obvious reasons.
CashPickup.Velocity = TossVel;
CashPickup.DroppedBy = Controller;
CashPickup.InitDroppedPickupFor(None);
PlayerReplicationInfo.Score -= Amount;
if ( Level.Game.NumPlayers > 1 && Level.TimeSeconds - LastDropCashMessageTime > DropCashMessageDelay )
PlayerController(Controller).Speech('AUTO', 4, "");
// Hack to get Slot machines to accept dosh that's thrown inside their collision cylinder.
ForEach CashPickup.TouchingActors(class 'Actor', A) {
if( A.IsA('KF_Slot_Machine') )
A.Touch(Cashpickup);
}
}
// end of copy
CashTossTimer = Level.TimeSeconds+0.1f;
if( LongTossCashTimer<Level.TimeSeconds ){
LongTossCashTimer = Level.TimeSeconds+5.f;
LongTossCashCount = 0;
}
else
++LongTossCashCount;
}
simulated function DoHitCamEffects(vector HitDirection, float JarrScale, float BlurDuration, float JarDurationScale )
{
if( KFPC!=none && Viewport(KFPC.Player)!=None )
Super(KFHumanPawn_Story).DoHitCamEffects(HitDirection,JarrScale, BlurDuration, JarDurationScale);
}
// fixes critical bug:
// Assertion failed: inst->KPhysRootIndex != INDEX_NONE && inst->KPhysLastIndex != INDEX_NONE [File:.\KSkeletal.cpp] [Line: 595]
simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
{
if ( DamageType.default.bSkeletize )
DamageType.default.bSkeletize = false;
super.PlayDying(DamageType, HitLoc);
}
simulated function Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
{
if ( ScrnPlayerController(Controller) != none && !ScrnPlayerController(Controller).bDestroying ) {
HealthBeforeDeath = Health;
}
super.Died(Killer, damageType, HitLocation);
}
function PlayerChangedTeam()
{
if ( ScrngameType(Level.Game) != none && !ScrngameType(Level.Game).ShouldKillOnTeamChange(self) )
return;
Died( None, class'DamageType', Location );
}
simulated function Setup(xUtil.PlayerRecord rec, optional bool bLoadNow)
{
local ScrnPlayerController PC;
local string CN;
PC = ScrnPlayerController(Controller);
if ( PC != none && IsLocallyControlled() ) {
// check this only on player side, because it stores
// RedCharacter & BlueCharacter in the config
CN = rec.DefaultName;
if ( !PC.ValidateCharacter(CN) ) {
// character invalid, change it valid one, which was set up in ValidateCharacter()
rec = class'xUtil'.static.FindPlayerRecord(CN);
PC.ChangeCharacter(CN);
}
}
super.Setup(rec, bLoadNow);
}
// disable shopping at enemy trader
function bool CanBuyNow()
{
local ShopVolume Shop, MyShop, EnemyShop;
local TSCGameReplicationInfo TSCGRI;
local bool bAtEnemyShop;
if( KFGameType(Level.Game)==None || KFGameType(Level.Game).bWaveInProgress
|| PlayerReplicationInfo==None || PlayerReplicationInfo.Team==None )
return False;
TSCGRI = TSCGameReplicationInfo(Level.GRI);
if ( TSCGRI == none ) {
MyShop = KFGameReplicationInfo(Level.GRI).CurrentShop;
EnemyShop = none;
}
else if ( PlayerReplicationInfo.Team.TeamIndex == 0 ) {
MyShop = TSCGRI.CurrentShop;
EnemyShop = TSCGRI.BlueShop;
}
else {
MyShop = TSCGRI.BlueShop;
EnemyShop = TSCGRI.CurrentShop;
}
foreach TouchingActors(Class'ShopVolume',Shop) {
if ( Shop == MyShop || (Shop.bAlwaysEnabled && Shop != EnemyShop) ) {
ScrnPlayerController(Controller).bShoppedThisWave = true;
return True;
}
bAtEnemyShop = bAtEnemyShop || Shop == EnemyShop;
}
if ( bAtEnemyShop ) {
PlayerController(Controller).ReceiveLocalizedMessage(class'TSCMessages', 300);
// throw player outside of enemy shop
if ( !EnemyShop.bTelsInit )
EnemyShop.InitTeleports();
if ( EnemyShop.TelList.Length > 0 )
EnemyShop.TelList[EnemyShop.TelList.Length-1].Accept(self, EnemyShop);
}
Return False;
}
event Bump(actor Other)
{
local ZombieCrawler crawler;
super.Bump(Other);
if ( class'ScrnBalance'.default.Mut.bBeta ) {
// push crawlers away
crawler = ZombieCrawler(Other);
if ( crawler != none && crawler.health > 0 && !crawler.bPouncing //&& !crawler.bShotAnim
&& (LastDamagedBy != crawler || Level.TimeSeconds - LastDamageTime > 0.2) )
{
crawler.Velocity = GroundSpeed * 0.5 * normal(Velocity);
}
}
}
defaultproperties
{
HealthRestoreRate=7.000000
NoVestClass=Class'ScrnBalanceSrv.ScrnNoVestPickup'
StandardVestClass=Class'ScrnBalanceSrv.ScrnCombatVestPickup'
LightVestClass=Class'ScrnBalanceSrv.ScrnLightVestPickup'
CurrentVestClass=Class'ScrnBalanceSrv.ScrnNoVestPickup'
bCheckHorzineArmorAch=True
strNoSpawnCashToss="Can not drop starting cash"
HeadShotSound=Sound'ProjectileSounds.Bullets.impact_metal09'
TraderSpeedBoost=1.500000
BlameStrM99="%p blamed for using a Noobgun"
BlameStrRPG="%p blamed for buying an RPG on Boss wave"
ShieldStrengthMax=0.000000
MaxFallSpeed=750.000000
}