class ScrnAchHandlerBase extends Info abstract; var protected ScrnGameRules GameRules; const IGNORE_STAT = 0x7FFFFFFF; // Damage flags // copy-pasted from ScrnGameRules for easy access const DF_STUNNED = 0x01; // stun shot const DF_RAGED = 0x02; // rage shot const DF_STUPID = 0x04; // damage shouldn't be done to this zed const DF_DECAP = 0x08; // decapitaion shot const DF_HEADSHOT = 0x10; // 100% sure that this damage was made by headshot function PostBeginPlay() { FindGameRules(); } function FindGameRules() { local GameRules G; if ( GameRules != none ) return; for ( G=Level.Game.GameRulesModifiers; G!=None; G=G.NextGameRules ) { if ( ScrnGameRules(G) != none ) { GameRules = ScrnGameRules(G); GameRules.RegisterAchHandler(self); return; } } log(GetItemName(String(self)) $ ": unable to find ScrnGameRules. Trying again in 5 seconds.", 'ScrnBalance'); SetTimer(5.0, false); } function Timer() { FindGameRules(); } // ----------------------------------------------------------------------------------- // STATIC FUNCTIONS // ----------------------------------------------------------------------------------- /* Helper function for quickly determining whether a pawn is rotated at a point in space. original authors : Alex Quick, Ron Prestenback. * @param Pawn pawn, which rotation needs to be checked * @param TargetLocation the location to check * @param MinDotResult a value between 0 and 1 representing the minimum dot result value that should be * considered acceptable for testing whether the location is within our field of * view. Default value (0) is corresponds to a maximum angle of around 180 degrees * * @return TRUE if the location is within the angle specified from the pawn's rotation. */ static function bool IsRotatedAtLocation(Pawn Pawn, vector TargetLocation, optional float MinDotResult) { local vector DirectionNormal; local float ViewAngleCosine; // get the normalized distance between the two locations DirectionNormal = Normal(TargetLocation - Pawn.Location); // get the dot result of the pawn's rotation and the target location ViewAngleCosine = DirectionNormal dot vector(Pawn.Rotation); // determine if the angle between pawn's rotation and the target location is within range. // log("IS LOOKING AT LOCATION - "@ViewAngleCosine@" Required : "@MinDotResult); return ViewAngleCosine >= MinDotResult; } // returns number of not triggered pipeboms, which detonation range covers Pawn's location // and are in lone of sight of a given pawn static function int InPipeBombRange(Pawn Pawn, float PipeDetonationRange) { local PipeBombProjectile pipe; local int count; local Controller C; C = Pawn.Controller; if ( C == none ) return 0; foreach Pawn.VisibleCollidingActors( class 'PipeBombProjectile', pipe, PipeDetonationRange, Pawn.Location ) { if ( !pipe.bTriggered && !pipe.bHasExploded && !pipe.bDisintegrated && !pipe.bEnemyDetected && IsRotatedAtLocation(Pawn, pipe.Location, 0.5) ) count++; } return count; } //returns how much damage Instigator made to Victim static function int GetTotalDamageMade(KFMonster Victim, Pawn Instigator) { if ( Victim == none || Instigator == none ) return 0; return GetTotalDamageMadeC(KFMonsterController(Victim.Controller), Instigator.Controller); } //returns how much damage PC made to MC static function int GetTotalDamageMadeC(KFMonsterController MC, Controller PC) { local int i; if ( MC == none || PC == none ) return 0; for ( i = 0; i < MC.KillAssistants.Length; ++i ) { if ( PC == MC.KillAssistants[i].PC ) return MC.KillAssistants[i].Damage; } return 0; } static function bool IsPistolDamage(ScrnPlayerInfo InstigatorSPI, class DamageType) { local KFPlayerReplicationInfo KFPRI; if ( DamageType == none || InstigatorSPI == none || InstigatorSPI.PlayerOwner == none ) return false; KFPRI = KFPlayerReplicationInfo(InstigatorSPI.PlayerOwner.PlayerReplicationInfo); if ( KFPRI == none ) return false; // Gunslinger gets damage bonus on all pistols (even level 0). // So if output damage > input damage, DamageType is pistol damage return class'ScrnVetGunslinger'.static.AddDamage(KFPRI, none, none, 100, DamageType) > 100; } static function bool IsAssaultRifleDamage(ScrnPlayerInfo InstigatorSPI, class DamageType) { local KFPlayerReplicationInfo KFPRI; if ( DamageType == none || InstigatorSPI == none || InstigatorSPI.PlayerOwner == none ) return false; KFPRI = KFPlayerReplicationInfo(InstigatorSPI.PlayerOwner.PlayerReplicationInfo); if ( KFPRI == none ) return false; return class'ScrnVetCommando'.static.AddDamage(KFPRI, none, none, 100, DamageType) > 100; } // returns true, if player is using medic perk static function bool IsMedic(ScrnPlayerInfo SPI) { local KFPlayerReplicationInfo KFPRI; if ( SPI == none || SPI.PlayerOwner == none ) return false; KFPRI = KFPlayerReplicationInfo(SPI.PlayerOwner.PlayerReplicationInfo); if ( KFPRI != none && KFPRI.ClientVeteranSkill != none ) return KFPRI.ClientVeteranSkill.static.GetHealPotency(KFPRI) > 1.01; return false; } // ----------------------------------------------------------------------------------- // give achievement to all players, except ExcludeSPI final function Ach2All(name AchID, int Inc, optional ScrnPlayerInfo ExcludeSPI, optional TeamInfo Team) { if ( GameRules != none ) GameRules.ProgressAchievementForAllPlayers(AchID, Inc, false, ExcludeSPI, Team); } // give achievement to alive players, except ExcludeSPI final function Ach2Alive(name AchID, int Inc, optional ScrnPlayerInfo ExcludeSPI, optional TeamInfo Team) { if ( GameRules != none ) GameRules.ProgressAchievementForAllPlayers(AchID, Inc, true, ExcludeSPI, Team); } /** * Function is called every time after the start of new wave * @param WaveNum - wave number, where 0 is first wave */ function WaveStarted(byte WaveNum) { } /** * Function is called every time after the end of new wave * ScrnPlayerInfos aren't reset yet and can be used freely here * @param WaveNum - wave number, where 0 is first wave */ function WaveEnded(byte WaveNum) { } // Function is called only when squad is survived. // Map name without .rom, e.g. "KF-Farm". MapName is already checked for GameRules.MapAliases before // calling GameWon() function GameWon(string MapName) { } // override this to catch all NetDamage() calls passed to GameRules. // NetDamage is executed after MonsterDamaged() or PlayerDamaged() function NetDamage( int Damage, pawn injured, pawn instigatedBy, vector HitLocation, out vector Momentum, class DamType ) {} // override this to catch all ScoreKill() calls passed to GameRules. // score killed is executed after MonsterDied() or PlayerDied() function ScoreKill(Controller Killer, Controller Killed) {} // player-to-monster damages // called from ScrnPlayerInfo.MadeDamage() // At this moment InstigatorInfo.LastDmgTime still stores previos damage time (not the given one). // So time between subsequent damages can be calculated by Level.TimeSeconds - InstigatorInfo.LastKillTime function MonsterDamaged(int Damage, KFMonster Victim, ScrnPlayerInfo InstigatorInfo, class DamType, bool bIsHeadshot, bool bWasDecapitated) {} // monster got killed by a player // called from ScrnPlayerInfo.KilledMonster() // At this moment KillerInfo.LastKillTime still stores previos kill time (not the given one). // So time between subsequent kills can be calculated by Level.TimeSeconds - KillerInfo.LastKillTime function MonsterKilled(KFMonster Victim, ScrnPlayerInfo KillerInfo, class DamType) {} // player took a hit from monster // called from ScrnPlayerInfo.TookDamage() function PlayerDamaged(int Damage, ScrnPlayerInfo VictimSPI, KFMonster InstigatedBy, class DamType) {} // player got killed // called from ScrnPlayerInfo.Died() // DeadPlayerInfo already has bDead = true and increased Deaths function PlayerDied(ScrnPlayerInfo DeadPlayerInfo, Controller Killer, class DamType) {} /** * Triggers when end game boss spawned on the map. * This function is called before KFMonster.PostBeginPlay(), where difficulty multipliers are applied, * so it can't be used for checking actual monster parameters such as health or damage! * @param EndGameBoss The monster to spawned as end game boss (usually Patriarch) */ function BossSpawned(KFMonster EndGameBoss) {} /** * Triggers when monster of a new class first time spawns in the game, i.e. it triggers on first Clot, first Gorefast etc. * This function is called before KFMonster.PostBeginPlay(), where difficulty multipliers are applied, * so it can't be used for checking actual monster parameters such as health or damage! * * @param Monster The momnster who spawned first time in the game */ function MonsterIntroduced(KFMonster Monster) {} // Functions below are triggered, if stat value reaches a given number. // AchHandler must return the minimal number, which it wants to be triggered. // If AchHandler isn't interested in a given stat, it must return IGNORE_STAT. // Keep in mind that return value is used as recomendation, not as a rule. It means that even // if function had returned 5, there are no guaranties that it won't be triggered with a lower value // e.g. 3. So input value must be checked always. /** * Triggers when player scored multiple headshots in a row. * @param SPI Intigator's ScrnPlayerInfo record * @param Count Headhot count in a row * @return Minimal headshot count to be reached before the next call of this function */ function int RowHeadhots(ScrnPlayerInfo SPI, int Count) { return IGNORE_STAT; } /** * Triggers when player scored multiple headshots in a row with one weapon. * @param SPI Intigator's ScrnPlayerInfo record * @param Weapon Weapon that PROBABLY was used to produce this damage type * @param DamType Damage type that was catched by GameRules * @param Count Headhot count in a row * @return Minimal headshot count to be reached before the next call of this function */ function int WRowHeadhots(ScrnPlayerInfo SPI, KFWeapon Weapon, class DamType, int Count) { return IGNORE_STAT; } /** * Triggers when player scored multiple headshots per single fire * (due to bullet penetration or F/A mode without releasing a trigger). * @param SPI Intigator's ScrnPlayerInfo record * @param Weapon Weapon that PROBABLY was used to produce this damage type * @param DamType Damage type that was catched by GameRules * @param Count Headhot count scored without releasing a fire trigger * @return Minimal headshot count to be reached before the next call of this function */ function int WInstantHeadhots(ScrnPlayerInfo SPI, KFWeapon Weapon, class DamType, int Count) { return IGNORE_STAT; } // Same as WInstantHeadhots, but triggers on headshots without reloading function int WHeadshotsPerMagazine(ScrnPlayerInfo SPI, KFWeapon Weapon, class DamType, int Count) { return IGNORE_STAT; } /** * Triggers when players scored multiple kills with one shot or without releasing a trigger * (depends from a weapon). Sometimes shot isn't registred and multiple shots are registred as one. * So DeltaTime needs to be checked too. * * @param SPI Intigator's ScrnPlayerInfo record * @param Weapon Weapon that PROBABLY was used to produce this damage type * @param DamType Damage type that was catched by GameRules * @param Count Kill count in one shot * @param DeltaTime time between last and previous kill * @return Minimal headshot count to be reached before the next call of this function */ function int WKillsPerShot(ScrnPlayerInfo SPI, KFWeapon Weapon, class DamType, int Count, float DeltaTime) { return IGNORE_STAT; } // Same as WKillsPerShot, but triggers on kills without reloading function int WKillsPerMagazine(ScrnPlayerInfo SPI, KFWeapon Weapon, class DamType, int Count) { return IGNORE_STAT; } // number of decapitaions per shot function int WDecapsPerShot(ScrnPlayerInfo SPI, KFWeapon Weapon, class DamType, int Count, float DeltaTime) { return IGNORE_STAT; } // Same as WDecapsPerShot, but triggers on kills without reloading function int WDecapsPerMagazine(ScrnPlayerInfo SPI, KFWeapon Weapon, class DamType, int Count) { return IGNORE_STAT; } // amount of damage per shot function int WDamagePerShot(ScrnPlayerInfo SPI, KFWeapon Weapon, class DamType, int Damage, float DeltaTime) { return IGNORE_STAT; } // Same as WDamagePerShot, but triggers on kills without reloading function int WDamagePerMagazine(ScrnPlayerInfo SPI, KFWeapon Weapon, class DamType, int Damage) { return IGNORE_STAT; } /** * Triggers each time when one player healed another one. Doesn't trigger if player already had 100% hp. * @param HealAmount the amount of health Patient received * @param Patient player, who received healing * @param InstigatorSPI Instigator's player info. Player, who made healing. * @param MedicGun Instigator's weapon, which was used for healing * */ function HealingMade(int HealAmount, ScrnHumanPawn Patient, ScrnPlayerInfo InstigatorSPI, KFWeapon MedicGun) {} /** * Triggers when player picks up the cash. ReceiverInfo.CashReceived and CashFound and DonatorInfo.CashDonated, * as well as their PerWave variables, already include CashAmount. * Function isn't triggered, when player picks up own cash. * * @param CashAmount amount of cash picked up * @param ReceiverInfo player, who received cash (always != none) * @param DonatorInfo player, who dropped the cash (can be none) * @param bDroppedCash if true, cash was dropped by a player. False means it was spawned on the map. * There can be situations when bDroppedCash=true and DonatorInfo=none, so * DonatorInfo must be always checked for none. */ function PickedCash(int CashAmount, ScrnPlayerInfo ReceiverInfo, ScrnPlayerInfo DonatorInfo, bool bDroppedCash) {} /** * Triggers when player picks up a weapon. Weapon is not added to player's inventory yet. * Function is triggered on picking up own weapons too. * @param SPI Player's info, who picked up a weapon * @param WeaponPickup The weapon picked up by the player */ function PickedWeapon(ScrnPlayerInfo SPI, KFWeaponPickup WeaponPickup) {} /** * Triggers when player picks up an item from the ground, excluding cash and weapons, * which have own trigger functions. Item is not added to player's inventory yet. * Function is triggered on picking up own items too. * @param SPI Player's info, who picked up an item * @param Item The item picked up by the player. */ function PickedItem(ScrnPlayerInfo SPI, Pickup Item) {} /** * Triggers every time players reloads weapon. * Per-magazine weapon stats are not nulled at this moment yet (e.g. KillsPerMagazine) * @param SPI Player's info, who picked up an item * @param W Reloaded Weapon */ function WeaponReloaded(ScrnPlayerInfo SPI, KFWeapon W) {} defaultproperties { }