Browse Source
This feature addresses several bugs related to pipe bombs: 1. Gaining extra explosive damage by shooting pipes with a high fire rate weapon; 2. Other players exploding one's pipes; 3. Corpses and story NPCs exploding nearby pipes; 4. Pipes being stuck in places where they cannot detect nearby enemies from and, therefore, not exploding.master
Anton Tarasenko
4 years ago
5 changed files with 603 additions and 0 deletions
@ -0,0 +1,422 @@
|
||||
/** |
||||
* This feature addresses several bugs related to pipe bombs: |
||||
* 1. Gaining extra explosive damage by shooting pipes with |
||||
* a high fire rate weapon; |
||||
* 2. Other players exploding one's pipes; |
||||
* 3. Corpses and story NPCs exploding nearby pipes; |
||||
* 4. Pipes being stuck in places where they cannot detect nearby |
||||
* enemies and, therefore, not exploding. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FixPipes extends Feature |
||||
config(AcediaFixes); |
||||
|
||||
/** |
||||
* There are two main culprits for the issues we are interested in: |
||||
* `TakeDamage()` and `Timer()` methods. `TakeDamage()` lacks sufficient checks |
||||
* for instigator allows for ways to damage it, causing explosion. It also |
||||
* lacks any checks for whether pipe bom already exploded, allowing players to |
||||
* damage it several time before it's destroyed, causing explosion every time. |
||||
* `Timer()` method simply contains a porximity check for hostile pawns that |
||||
* wrongfully detects certain actor (like `KF_StoryNPC` or players' corpses) |
||||
* and cannot detect zeds around itself if pipe is placed in a way that |
||||
* prevents it from having a direct line of sight towards zeds. |
||||
* To fix our issues we have to somehow overload both of these methods, |
||||
* which is impossible while keeping Acedia server-side. So we will have to |
||||
* do some hacks. |
||||
* |
||||
* `TakeDamage()`. To override this method's behavior we will remove actor |
||||
* collision from pipe bombs, preventing it from being directly damaged, then |
||||
* add an `ExtendedZCollision` subclass around it to catch `TakeDamage()` |
||||
* events that would normally target pipe bombs themselves. There we can add |
||||
* additional checks to help us decide whether pipe bombs should actually |
||||
* be damaged. |
||||
* |
||||
* `Timer()`. It would be simple to take control of this method by, say, |
||||
* spamming `SetTimer(0.0, false)` every tick and then executing our own logic |
||||
* elsewhere. However we only care about changin `Timer()`'s logic when pipe |
||||
* bomb attempts to detect cnearby enemies and do not want to manage the rest |
||||
* of `Timer()`'s logic. |
||||
* Pipe bombs work like this: after being thrown they fall until they land, |
||||
* invoking `Landed()` and `HitWall()` calls that start up the timer. Then |
||||
* timer uses `ArmingCountDown` variable to count down the time for pipe to |
||||
* "arm", that is, start functioning. It's after that that pipe starts to try |
||||
* and detect nearby enemies until it decides to start exploding sequence, |
||||
* instantly exploded with damage or disintegrated by siren's scream. |
||||
* We want to catch the moment when `ArmingCountDown` reaches zero and |
||||
* only then disable the timer. Then case we'll only need to reimplement logic |
||||
* that takes care of enemy detection. We also do not need to constantly |
||||
* re-reset the timer, since any code that will restart it would mean that |
||||
* pipe bomb no longer needs to look for enemies. |
||||
* When we detect enough enemies nearby or a change in pipe bomb's state |
||||
* due to outside factors (being exploded or disintegrated) we simply need to |
||||
* start it's the timer (if it was not done for us already). |
||||
*/ |
||||
|
||||
// NOTE #1: setting either of `preventMassiveDamage` or |
||||
// `preventSuspiciousDamage` might change how and where pipes might fall on |
||||
// the ground (for example, pipe hats shoul not work anymore). In most cases, |
||||
// however, there should be no difference from vanilla gameplay. |
||||
// Setting this to `true` fixes a mechanic that allows pipes to deal extra |
||||
// damage when detonated with a high fire rate weapons: pipes will now always |
||||
// deal the same amount of damage. |
||||
var public const config bool preventMassiveDamage; |
||||
// It's possible for teammates to explode one's pipe under certain |
||||
// circumstances. Setting this setting to `true` will prevent that |
||||
// from happening. |
||||
var public const config bool preventSuspiciousDamage; |
||||
// Setting this to `true` will prevent pipe bombs from being detonated by |
||||
// the nearby corpses on other player. |
||||
var public const config bool preventCorpseDetonation; |
||||
// Setting this to `true` will prevents pipe bombs from being detonated by |
||||
// nearby KFO NPCs (Ringmaster Lockheart). |
||||
var public const config bool preventNPCDetonation; |
||||
// Certain spots prevent pipe bombs from having a line of sight to the |
||||
// nearby zeds, preventing them from ever exploding. This issue can be resolves |
||||
// by checking zed's proximity not to the pipe itself, but to a point directly |
||||
// above it (20 units above by default). |
||||
// If you wish to disable this part of the feature - set it to zero or |
||||
// negative value. Otherwise it is suggested to keep it at `20`. |
||||
var public const config float proximityCheckElevation; |
||||
|
||||
// Since this feature needs to do proximity check instead of pipes, we will |
||||
// need to track all the pipes we will be doing checks for. |
||||
// This struct contains all reference to the pipe itself and everything |
||||
// else we need to track. |
||||
struct PipeRecord |
||||
{ |
||||
// Pipe that this record tracks |
||||
var PipeBombProjectile pipe; |
||||
// Each pipe has a separate timer for their scheduled proximity checks, |
||||
// so we need a separate variable to track when that time comes for |
||||
// each and every pipe |
||||
var float timerCountDown; |
||||
// `true` if we have already intercepted (and replaced with our own) |
||||
// the proximity check for the pipe in this record |
||||
var bool proximityCheckIntercepted; |
||||
// Reference to the `ExtendedZCollision` we created to catch |
||||
// `TakeDamage()` event. |
||||
var PipesSafetyCollision safetyCollision; |
||||
}; |
||||
var private array<PipeRecord> pipeRecords; |
||||
|
||||
// Store the `bAlwaysRelevant` of the `class'PipeBombProjectile'` before this |
||||
// feature activated to restore it in case it gets disabled |
||||
// (we need to change `bAlwaysRelevant` in order to catch events of |
||||
// pipe bombs spawning). |
||||
var private bool pipesRelevancyFlag; |
||||
|
||||
protected function OnEnabled() |
||||
{ |
||||
local PipeBombProjectile nextPipe; |
||||
pipesRelevancyFlag = class'PipeBombProjectile'.default.bAlwaysRelevant; |
||||
class'PipeBombProjectile'.default.bGameRelevant = false; |
||||
// Fix pipes that are already lying about on the map |
||||
foreach level.DynamicActors(class'KFMod.PipeBombProjectile', nextPipe) { |
||||
RegisterPipe(nextPipe); |
||||
} |
||||
} |
||||
|
||||
protected function OnDisabled() |
||||
{ |
||||
local int i; |
||||
class'PipeBombProjectile'.default.bGameRelevant = pipesRelevancyFlag; |
||||
for (i = 0; i < pipeRecords.length; i += 1) { |
||||
ReleasePipe(pipeRecords[i]); |
||||
} |
||||
pipeRecords.length = 0; |
||||
} |
||||
|
||||
// Adds new pipe to our list and does necessary steps to replace logic of |
||||
// `TakeDamage()` and `Timer()` methods. |
||||
public final function RegisterPipe(PipeBombProjectile newPipe) |
||||
{ |
||||
local int i; |
||||
local PipeRecord newRecord; |
||||
if (newPipe == none) { |
||||
return; |
||||
} |
||||
// Check whether we have already added this pipe |
||||
for (i = 0; i < pipeRecords.length; i += 1) |
||||
{ |
||||
if (pipeRecords[i].pipe == newPipe) { |
||||
return; |
||||
} |
||||
} |
||||
newRecord.pipe = newPipe; |
||||
// Setup `PipesSafetyCollision` for catching `TakeDamage()` events |
||||
// (only if we need to according to settings) |
||||
if (NeedSafetyCollision()) |
||||
{ |
||||
newRecord.safetyCollision = |
||||
class'PipesSafetyCollision'.static.ProtectPipes(newPipe); |
||||
} |
||||
newRecord.pipe = newPipe; |
||||
pipeRecords[pipeRecords.length] = newRecord; |
||||
// Intercept proximity checks (only if we need to according to settings) |
||||
if (NeedManagedProximityChecks()) |
||||
{ |
||||
// We do this after we have added new pipe record to the complete list |
||||
// so that we can redo the check early for |
||||
// the previously recorded pipes |
||||
InterceptProximityChecks(); |
||||
} |
||||
} |
||||
|
||||
// Rolls back our changes to the pipe in the given `PipeRecord`. |
||||
public final function ReleasePipe(PipeRecord pipeRecord) |
||||
{ |
||||
if (pipeRecord.safetyCollision != none) |
||||
{ |
||||
pipeRecord.safetyCollision.TurnOff(); |
||||
pipeRecord.safetyCollision = none; |
||||
} |
||||
if (pipeRecord.proximityCheckIntercepted && pipeRecord.pipe != none) |
||||
{ |
||||
pipeRecord.proximityCheckIntercepted = false; |
||||
if (IsPipeDoingProximityChecks(pipeRecord.pipe)) { |
||||
pipeRecord.pipe.SetTimer(pipeRecord.timerCountDown, true); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Checks whether we actually need to use replace logic of `TakeDamage()` |
||||
// method according to settings. |
||||
private final function bool NeedSafetyCollision() |
||||
{ |
||||
if (preventMassiveDamage) return true; |
||||
if (preventSuspiciousDamage) return true; |
||||
return false; |
||||
} |
||||
|
||||
// Checks whether we actually need to use replace logic of `Timer()` |
||||
// method according to settings. |
||||
private final function bool NeedManagedProximityChecks() |
||||
{ |
||||
if (preventCorpseDetonation) return true; |
||||
if (preventNPCDetonation) return true; |
||||
if (proximityCheckElevation > 0.0) return true; |
||||
return false; |
||||
} |
||||
|
||||
// Removes dead records with pipe instances turned into `none` |
||||
private final function CleanPipeRecords() |
||||
{ |
||||
local int i; |
||||
while (i < pipeRecords.length) |
||||
{ |
||||
if (pipeRecords[i].pipe == none) { |
||||
pipeRecords.Remove(i, 1); |
||||
} |
||||
else { |
||||
i += 1; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Tries to replace logic of `SetTimer()` with our own for every pipe we |
||||
// keep track of that: |
||||
// 1. Started doing proximity checks; |
||||
// 2. Have not yet had it's logic replaced. |
||||
private final function InterceptProximityChecks() |
||||
{ |
||||
local int i; |
||||
for (i = 0; i < pipeRecords.length; i += 1) |
||||
{ |
||||
if (pipeRecords[i].proximityCheckIntercepted) continue; |
||||
if (pipeRecords[i].pipe == none) continue; |
||||
if (IsPipeDoingProximityChecks(pipeRecords[i].pipe)) |
||||
{ |
||||
pipeRecords[i].pipe.SetTimer(0, false); |
||||
// Line 123 of "PipeBombProjectile.uc": `SetTimer(1.0,True);` |
||||
pipeRecords[i].timerCountDown = 1.0; |
||||
pipeRecords[i].proximityCheckIntercepted = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Assumes `pipe != none` |
||||
private final function bool IsPipeDoingProximityChecks(PipeBombProjectile pipe) |
||||
{ |
||||
// These checks need to pass so that `Timer()` call from |
||||
// "PipeBombProjectile.uc" enters the proximity check code |
||||
// (starting at line 131) |
||||
if (pipe.bHidden) return false; |
||||
if (pipe.bTriggered) return false; |
||||
if (pipe.bEnemyDetected) return false; |
||||
return (pipe.ArmingCountDown < 0); |
||||
} |
||||
|
||||
// Checks what pipes have their timers run out and doing proximity checks |
||||
// for them |
||||
private final function PerformProximityChecks(float delta) |
||||
{ |
||||
local int i; |
||||
local Vector checkLocation; |
||||
for (i = 0; i < pipeRecords.length; i += 1) |
||||
{ |
||||
if (pipeRecords[i].pipe == none) continue; |
||||
// `timerCountDown` does not makes sense for pipes that |
||||
// are not doing proxiity checks |
||||
if (!IsPipeDoingProximityChecks(pipeRecords[i].pipe)) continue; |
||||
|
||||
pipeRecords[i].timerCountDown -= delta; |
||||
if (pipeRecords[i].timerCountDown <= 0) |
||||
{ |
||||
checkLocation = pipeRecords[i].pipe.location; |
||||
if (proximityCheckElevation > 0) { |
||||
checkLocation.z += proximityCheckElevation; |
||||
} |
||||
// This method repeats vanilla logic with some additional checks |
||||
// and sets new timers by itself |
||||
DoPipeProximityCheck(pipeRecords[i], checkLocation); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Assumes `pipeRecord.pipe != none` |
||||
// Original code is somewhat messy and was reworked in this more manageble |
||||
// form as core logic is simple - for every nearby `Pawn` we increase the |
||||
// percieved level for the pipe and when it reaches certain threshold |
||||
// (`threatThreshhold = 1.0`) pipe explodes. |
||||
// In native logic there are three cases for increasing threat level: |
||||
// 1. Zeds increase it by a certain predefined |
||||
// (in `KFMonster` class amount); |
||||
// 2. Instigator (and his team, in case friendly fire is enabled and |
||||
// they can be hurt by pipes) raise threat level by a negligible |
||||
// amount (`0.001`) that will not lead to pipes exploding, but |
||||
// will cause them to beep faster, warning about the danger; |
||||
// 3. Some wacky code to check whether the `Pawn` is on the same team. |
||||
// In a regualar killing floor game only something that is neither |
||||
// player or zed can fit that. This is what causes corpses and |
||||
// KFO NPCs to explode pipes. |
||||
// Threat level is not directly set in vanilla code for |
||||
// this case, instead performing two operations |
||||
// `bEnemyDetected=true;` and `SetTimer(0.15,True);`. |
||||
// However, given the code that follows these calls, executing them |
||||
// is equivalent to adding `threatThreshhold` to the current |
||||
// threat level. Which is what we do here instead. |
||||
private final function DoPipeProximityCheck( |
||||
out PipeRecord pipeRecord, |
||||
Vector checkLocation) |
||||
{ |
||||
local Pawn checkPawn; |
||||
local float threatLevel; |
||||
local PipeBombProjectile pipe; |
||||
pipe = pipeRecord.pipe; |
||||
pipe.bAlwaysRelevant = false; |
||||
pipe.PlaySound(pipe.beepSound,, 0.5,, 50.0); |
||||
// Out rewritten logic, which should do exactly the same: |
||||
foreach VisibleCollidingActors( class'Pawn', checkPawn, |
||||
pipe.detectionRadius, checkLocation) |
||||
{ |
||||
threatLevel += GetThreatLevel(pipe, checkPawn); |
||||
// Explosion! No need to bother with the rest of the `Pawn`s. |
||||
if (threatLevel >= pipe.threatThreshhold) { |
||||
break; |
||||
} |
||||
} |
||||
// Resume vanilla code from "PipeBombProjectile.uc", lines from 169 to 181: |
||||
if(threatLevel >= pipe.threatThreshhold) |
||||
{ |
||||
pipe.bEnemyDetected = true; |
||||
pipe.SetTimer(0.15, true); |
||||
} |
||||
else if(threatLevel > 0) { |
||||
pipeRecord.timerCountDown = 0.5; |
||||
} |
||||
else { |
||||
pipeRecord.timerCountDown = 1.0; |
||||
} |
||||
} |
||||
|
||||
// Threat level calculations are moves to a separate method to make algorithm |
||||
// less cumbersome |
||||
private final function float GetThreatLevel( |
||||
PipeBombProjectile pipe, |
||||
Pawn checkPawn) |
||||
{ |
||||
local bool onSameTeam; |
||||
local bool friendlyFireEnabled; |
||||
local KFGameType kfGame; |
||||
local PlayerReplicationInfo playerRI; |
||||
local KFMonster zed; |
||||
if (pipe == none) return 0.0; |
||||
if (checkPawn == none) return 0.0; |
||||
|
||||
playerRI = checkPawn.playerReplicationInfo; |
||||
if (pipe.level != none) { |
||||
kfGame = KFGameType(pipe.level.game); |
||||
} |
||||
if (kfGame != none) { |
||||
friendlyFireEnabled = kfGame.friendlyFireScale > 0; |
||||
} |
||||
// Warn teammates about pipes |
||||
onSameTeam = playerRI != none && playerRI.team.teamIndex == pipe.placedTeam; |
||||
if (checkPawn == pipe.instigator || (friendlyFireEnabled && onSameTeam)) { |
||||
return 0.001; |
||||
} |
||||
// Count zed's threat score |
||||
zed = KFMonster(checkPawn); |
||||
if (zed != none) { |
||||
return zed.motionDetectorThreat; |
||||
} |
||||
// Managed checks |
||||
if (preventCorpseDetonation && checkPawn.health <= 0) { |
||||
return 0.0; |
||||
} |
||||
if (preventNPCDetonation && KF_StoryNPC(CheckPawn) != none) { |
||||
return 0.0; |
||||
} |
||||
// Something weird demands a special full-alert treatment. |
||||
// Some checks are removed: |
||||
// 1. Removed `checkPawn.Role == ROLE_Authority` check, since we are |
||||
// working on server exclusively; |
||||
// 2. Removed `checkPawn != instigator` since, if |
||||
// `checkPawn == pipe.instigator`, previous `if` block will |
||||
// prevent us from reaching this point |
||||
if( playerRI != none && playerRI.team.teamIndex != pipe.placedTeam |
||||
|| checkPawn.GetTeamNum() != pipe.placedTeam) |
||||
{ |
||||
// Full threat score |
||||
return pipe.threatThreshhold; |
||||
} |
||||
return 0.0; |
||||
} |
||||
|
||||
event Tick(float delta) |
||||
{ |
||||
CleanPipeRecords(); |
||||
if (NeedManagedProximityChecks()) |
||||
{ |
||||
InterceptProximityChecks(); |
||||
PerformProximityChecks(delta); |
||||
} |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
preventMassiveDamage = true |
||||
preventSuspiciousDamage = true |
||||
onlyZedsAndPlayersCanDetonate = true |
||||
preventCorpseDetonation = true |
||||
preventNPCDetonation = true |
||||
proximityCheckElevation = 20.0 |
||||
// Listeners |
||||
requiredListeners(0) = class'MutatorListener_FixPipes' |
||||
} |
@ -0,0 +1,39 @@
|
||||
/** |
||||
* Overloaded mutator events listener to register spawned pipe bombs. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class MutatorListener_FixPipes extends MutatorListenerBase |
||||
abstract; |
||||
|
||||
static function bool CheckReplacement(Actor other, out byte isSuperRelevant) |
||||
{ |
||||
local FixPipes pipesFix; |
||||
local PipeBombProjectile pipeProjectile; |
||||
pipeProjectile = PipeBombProjectile(other); |
||||
if (pipeProjectile == none) return true; |
||||
pipesFix = FixPipes(class'FixPipes'.static.GetInstance()); |
||||
if (pipesFix == none) return true; |
||||
|
||||
pipesFix.RegisterPipe(pipeProjectile); |
||||
return true; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
relatedEvents = class'MutatorEvents' |
||||
} |
@ -0,0 +1,105 @@
|
||||
/** |
||||
* This collision attaches itself to pipes to catch `TakeDamage()` events |
||||
* in their place and only propagating them after additional checks. |
||||
* Copyright 2021 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class PipesSafetyCollision extends ExtendedZCollision; |
||||
|
||||
// Static function that raplaces `PipeBombProjectile` damage detectino with |
||||
// safer `PipesSafetyCollision`'s one. |
||||
public final static function PipesSafetyCollision ProtectPipes( |
||||
PipeBombProjectile target) |
||||
{ |
||||
local PipesSafetyCollision newCollision; |
||||
if (target == none) return none; |
||||
|
||||
newCollision = target.Spawn(class'PipesSafetyCollision', target); |
||||
newCollision.SetCollision(true); |
||||
newCollision.SetCollisionSize( target.collisionRadius, |
||||
target.collisionHeight); |
||||
newCollision.SetLocation(target.location); |
||||
newCollision.SetPhysics(PHYS_None); |
||||
newCollision.SetBase(target); |
||||
newCollision.SetTimer(1.0, true); |
||||
target.SetCollision(false); |
||||
return newCollision; |
||||
} |
||||
|
||||
// Same method for checking suspicious `Pawn`s as in `FixFFHack`. |
||||
// Copy-pasting it once is fine-ish, but if we will need it for another fix - |
||||
// we will have to move it out into a general method, independed from |
||||
// particular fixes. |
||||
private function bool IsSuspicious(Pawn instigator) |
||||
{ |
||||
// Instigator vanished |
||||
if (instigator == none) return true; |
||||
|
||||
// Instigator already became spectator |
||||
if (KFPawn(instigator) != none) |
||||
{ |
||||
if (instigator.playerReplicationInfo != none) { |
||||
return instigator.playerReplicationInfo.bOnlySpectator; |
||||
} |
||||
return true; // Replication info is gone => suspicious |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
// Revert changes made by caller `PipesSafetyCollision`, letting corresponding |
||||
// pipes catch damage events on their own again. |
||||
public final function TurnOff() |
||||
{ |
||||
if (owner != none) { |
||||
owner.SetCollision(true); |
||||
} |
||||
SetOwner(none); |
||||
Destroy(); |
||||
} |
||||
|
||||
// `TakeDamage()` with added checks |
||||
function TakeDamage( |
||||
int damage, |
||||
Pawn instigator, |
||||
Vector hitlocation, |
||||
Vector momentum, |
||||
class<DamageType> damageType, |
||||
optional int hitIndex) |
||||
{ |
||||
local FixPipes pipesFix; |
||||
local PipeBombProjectile target; |
||||
target = PipeBombProjectile(owner); |
||||
if (target == none) return; |
||||
pipesFix = FixPipes(class'FixPipes'.static.GetInstance()); |
||||
if (pipesFix == none) return; |
||||
if (pipesFix.preventMassiveDamage && target.bTriggered) return; |
||||
if (pipesFix.preventSuspiciousDamage && IsSuspicious(instigator)) return; |
||||
|
||||
owner.TakeDamage( damage, instigator, hitlocation, |
||||
momentum, damageType, hitIndex); |
||||
} |
||||
|
||||
event Timer() |
||||
{ |
||||
if (owner == none) { |
||||
Destroy(); |
||||
} |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
Loading…
Reference in new issue