Browse Source
This feature addresses the bug that allows teammates to explode some of the player's projectiles by damaging them even when friendly fire is turned off, therefore killing the player (whether by accident or not). Problem is solved by "disarming" projectiles vulnerable to this friendly fire and replacing them with our own class of projectile that is spawned only on a server and does additional safety checks to ensure it will only explode when it is expected from it.master
Anton Tarasenko
4 years ago
15 changed files with 989 additions and 1 deletions
@ -0,0 +1,165 @@ |
|||||||
|
/** |
||||||
|
* This feature addresses the bug that allows teammates to explode some of |
||||||
|
* the player's projectiles by damaging them even when friendly fire is |
||||||
|
* turned off, therefore killing the player (whether by accident or not). |
||||||
|
* |
||||||
|
* Problem is solved by "disarming" projectiles vulnerable to this |
||||||
|
* friendly fire and replacing them with our own class of projectile that is |
||||||
|
* spawned only on a server and does additional safety checks to ensure it will |
||||||
|
* only explode when it is expected from it. |
||||||
|
* 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 FixProjectileFF extends Feature |
||||||
|
config(AcediaFixes); |
||||||
|
|
||||||
|
/** |
||||||
|
* All projectiles vulnerable to this bug (we consider only those that can |
||||||
|
* explode and harm the player) are derived from `ROBallisticProjectile`. When |
||||||
|
* one of them is spawned we will: |
||||||
|
* 1. Set it's damage parameters to zero, to ensure that it won't damage |
||||||
|
* anyone or anything if other measures fail; |
||||||
|
* 2. Disable it's collision, preventing it from being accessed via |
||||||
|
* `VisibleCollidingActors()` method that is usually used to damage |
||||||
|
* these projectiles. |
||||||
|
* Then we spawn our own version of the projectile with fixed |
||||||
|
* `TakeDamage()` that does additional check that either it's projectile's |
||||||
|
* owner that damages it or friendly fire is enabled. |
||||||
|
* To do this replacement we will need to catch the moment when vanilla |
||||||
|
* projectile spawns. Unfortunately, this cannot be done by default, since all |
||||||
|
* projectiles have `bGameRelevant` flag set to `true`, which prevents |
||||||
|
* `CheckReplacement()` from being called for them. So, when feature is |
||||||
|
* enabled, it forces all the projectiles we are interested in to have |
||||||
|
* `bGameRelevant = false`. |
||||||
|
* |
||||||
|
* Issue arises from the fact that these two projectiles can desyncronize: |
||||||
|
* old projectile, that client sees, might not be in the same location as the |
||||||
|
* new one (especially since we are disabling collisions for the old one), that |
||||||
|
* deals damage. There are two cases to consider, depending on |
||||||
|
* the `bNetTemporary` of the old projectile: |
||||||
|
* 1. `bNetTemporary == true`: projectile version on client side is |
||||||
|
* independent from the server-side's one. In this case there is |
||||||
|
* nothing we can do. In fact, vanilla game suffers from this very bug |
||||||
|
* that makes, say, M79 projectile fly past the zed it exploded |
||||||
|
* (usually after killing it). To deal with this we would need |
||||||
|
* the ability to affect client's code, which cannot be done in |
||||||
|
* the server mode. |
||||||
|
* 2. `bNetTemporary == true`: projectile version on client side is |
||||||
|
* actually syncronized with the server-side's one. In this case we |
||||||
|
* will simply make new projectile to constantly force the old one to |
||||||
|
* be in the same place at the same rotation. We will also propagate |
||||||
|
* various state-changing events such as exploding, disintegrating from |
||||||
|
* siren's scream or sticking to the wall/zed. That is, we will make |
||||||
|
* the old projectile (that client can see) "the face" of the new one |
||||||
|
* (that client cannot see). Only "The Orca Bomb Propeller" and |
||||||
|
* "SealSqueal Harpoon Bomber" fall into this group. |
||||||
|
*/ |
||||||
|
|
||||||
|
// By default (`ignoreFriendlyFire == false`), when friendly fire is enabled |
||||||
|
// (at any level), this fix allows other players to explode one's projectiles. |
||||||
|
// By setting this to `true` you will force this fix to prevent it no matter |
||||||
|
// what, even when server has full friendly fire enabled. |
||||||
|
var private config bool ignoreFriendlyFire; |
||||||
|
|
||||||
|
// Stores what pairs of projectile classes that describe what (vulnerable) |
||||||
|
// class must be repalced with what (protected class). It also remembers the |
||||||
|
// previous state of `bGameRelevant` for replacable class to restore it in case |
||||||
|
// this feature is disabled. |
||||||
|
struct ReplacementRule |
||||||
|
{ |
||||||
|
// `bGameRelevant` before this feature set it to `false` |
||||||
|
var bool relevancyFlag; |
||||||
|
var class<ROBallisticProjectile> vulnerableClass; |
||||||
|
var class<ROBallisticProjectile> protectedClass; |
||||||
|
}; |
||||||
|
var private const array<ReplacementRule> rules; |
||||||
|
|
||||||
|
protected function OnEnabled() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < rules.length; i += 1) |
||||||
|
{ |
||||||
|
if (rules[i].vulnerableClass == none) continue; |
||||||
|
if (rules[i].protectedClass == none) continue; |
||||||
|
rules[i].relevancyFlag = rules[i].vulnerableClass.default.bGameRelevant; |
||||||
|
rules[i].vulnerableClass.default.bGameRelevant = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected function OnDisabled() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < rules.length; i += 1) |
||||||
|
{ |
||||||
|
if (rules[i].vulnerableClass == none) continue; |
||||||
|
if (rules[i].protectedClass == none) continue; |
||||||
|
rules[i].vulnerableClass.default.bGameRelevant = rules[i].relevancyFlag; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Returns "fixed" class that no longer explodes from random damage |
||||||
|
public final static function class<ROBallisticProjectile> FindFixedClass( |
||||||
|
class<ROBallisticProjectile> projectileClass) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<ReplacementRule> rulesCopy; |
||||||
|
if (projectileClass == none) return none; |
||||||
|
|
||||||
|
rulesCopy = default.rules; |
||||||
|
for (i = 0; i < rulesCopy.length; i += 1) |
||||||
|
{ |
||||||
|
if (rulesCopy[i].vulnerableClass == projectileClass) { |
||||||
|
return rulesCopy[i].protectedClass; |
||||||
|
} |
||||||
|
} |
||||||
|
return projectileClass; |
||||||
|
} |
||||||
|
|
||||||
|
// Check if, according to this fix, projectiles should explode from |
||||||
|
// friendly fire |
||||||
|
// If `FixProjectileFF` in disabled always returns `false`. |
||||||
|
public final static function bool IsFriendlyFireAcceptable() |
||||||
|
{ |
||||||
|
local TeamGame gameType; |
||||||
|
local FixProjectileFF projectileFFFix; |
||||||
|
projectileFFFix = FixProjectileFF(GetInstance()); |
||||||
|
if (projectileFFFix == none) return false; |
||||||
|
if (projectileFFFix.ignoreFriendlyFire) return false; |
||||||
|
if (projectileFFFix.level == none) return false; |
||||||
|
gameType = TeamGame(projectileFFFix.level.game); |
||||||
|
if (gameType == none) return false; |
||||||
|
|
||||||
|
return gameType.friendlyFireScale > 0; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
ignoreFriendlyFire = false |
||||||
|
rules(0) = (vulnerableClass=class'KFMod.M79GrenadeProjectile',protectedClass=class'FixProjectileFFClass_M79GrenadeProjectile') |
||||||
|
rules(1) = (vulnerableClass=class'KFMod.M32GrenadeProjectile',protectedClass=class'FixProjectileFFClass_M32GrenadeProjectile') |
||||||
|
rules(2) = (vulnerableClass=class'KFMod.LAWProj',protectedClass=class'FixProjectileFFClass_LAWProj') |
||||||
|
rules(3) = (vulnerableClass=class'KFMod.M203GrenadeProjectile',protectedClass=class'FixProjectileFFClass_M203GrenadeProjectile') |
||||||
|
rules(4) = (vulnerableClass=class'KFMod.ZEDGunProjectile',protectedClass=class'FixProjectileFFClass_ZEDGunProjectile') |
||||||
|
rules(5) = (vulnerableClass=class'KFMod.ZEDMKIIPrimaryProjectile',protectedClass=class'FixProjectileFFClass_ZEDMKIIPrimaryProjectile') |
||||||
|
rules(6) = (vulnerableClass=class'KFMod.ZEDMKIISecondaryProjectile',protectedClass=class'FixProjectileFFClass_ZEDMKIISecondaryProjectile') |
||||||
|
rules(7) = (vulnerableClass=class'KFMod.FlareRevolverProjectile',protectedClass=class'FixProjectileFFClass_FlareRevolverProjectile') |
||||||
|
rules(8) = (vulnerableClass=class'KFMod.HuskGunProjectile',protectedClass=class'FixProjectileFFClass_HuskGunProjectile') |
||||||
|
rules(9) = (vulnerableClass=class'KFMod.SPGrenadeProjectile',protectedClass=class'FixProjectileFFClass_SPGrenadeProjectile') |
||||||
|
rules(10) = (vulnerableClass=class'KFMod.SealSquealProjectile',protectedClass=class'FixProjectileFFClass_SealSquealProjectile') |
||||||
|
// Listeners |
||||||
|
requiredListeners(0) = class'MutatorListener_FixProjectileFF' |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
/** |
||||||
|
* A helper class for `FixProjectileFF` that adds an instigator and |
||||||
|
* friendly fire checks to `TakeDamage()` method to avoid exploding projectiles |
||||||
|
* when not expected. |
||||||
|
* 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 FixProjectileFFClass_FlareRevolverProjectile |
||||||
|
extends FlareRevolverProjectile; |
||||||
|
|
||||||
|
function TakeDamage( |
||||||
|
int damage, |
||||||
|
Pawn instigatedBy, |
||||||
|
Vector hitLocation, |
||||||
|
Vector momentum, |
||||||
|
class<DamageType> damageType, |
||||||
|
optional int hitIndex) |
||||||
|
{ |
||||||
|
local bool canTakeThisDamage; |
||||||
|
if (damageType == class'SirenScreamDamage') |
||||||
|
{ |
||||||
|
Disintegrate(hitLocation, Vect(0, 0, 1)); |
||||||
|
return; |
||||||
|
} |
||||||
|
canTakeThisDamage = |
||||||
|
(instigatedBy == instigator) |
||||||
|
|| class'FixProjectileFF'.static.IsFriendlyFireAcceptable(); |
||||||
|
if (canTakeThisDamage && !bDud) { |
||||||
|
Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
RemoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
/** |
||||||
|
* A helper class for `FixProjectileFF` that adds an instigator and |
||||||
|
* friendly fire checks to `TakeDamage()` method to avoid exploding projectiles |
||||||
|
* when not expected. |
||||||
|
* 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 FixProjectileFFClass_HuskGunProjectile extends HuskGunProjectile; |
||||||
|
|
||||||
|
function TakeDamage( |
||||||
|
int damage, |
||||||
|
Pawn instigatedBy, |
||||||
|
Vector hitLocation, |
||||||
|
Vector momentum, |
||||||
|
class<DamageType> damageType, |
||||||
|
optional int hitIndex) |
||||||
|
{ |
||||||
|
local bool canTakeThisDamage; |
||||||
|
if (damageType == class'SirenScreamDamage') |
||||||
|
{ |
||||||
|
Disintegrate(hitLocation, Vect(0, 0, 1)); |
||||||
|
return; |
||||||
|
} |
||||||
|
canTakeThisDamage = |
||||||
|
(instigatedBy == instigator) |
||||||
|
|| class'FixProjectileFF'.static.IsFriendlyFireAcceptable(); |
||||||
|
if (canTakeThisDamage && !bDud) { |
||||||
|
Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
RemoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
/** |
||||||
|
* A helper class for `FixProjectileFF` that adds an instigator and |
||||||
|
* friendly fire checks to `TakeDamage()` method to avoid exploding projectiles |
||||||
|
* when not expected. |
||||||
|
* 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 FixProjectileFFClass_LAWProj extends LAWProj; |
||||||
|
|
||||||
|
function TakeDamage( |
||||||
|
int damage, |
||||||
|
Pawn instigatedBy, |
||||||
|
Vector hitLocation, |
||||||
|
Vector momentum, |
||||||
|
class<DamageType> damageType, |
||||||
|
optional int hitIndex) |
||||||
|
{ |
||||||
|
local bool canTakeThisDamage; |
||||||
|
if (damageType == class'SirenScreamDamage') |
||||||
|
{ |
||||||
|
Disintegrate(hitLocation, Vect(0, 0, 1)); |
||||||
|
return; |
||||||
|
} |
||||||
|
canTakeThisDamage = |
||||||
|
(instigatedBy == instigator) |
||||||
|
|| class'FixProjectileFF'.static.IsFriendlyFireAcceptable(); |
||||||
|
if (canTakeThisDamage && !bDud) { |
||||||
|
Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
RemoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
/** |
||||||
|
* A helper class for `FixProjectileFF` that adds an instigator and |
||||||
|
* friendly fire checks to `TakeDamage()` method to avoid exploding projectiles |
||||||
|
* when not expected. |
||||||
|
* 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 FixProjectileFFClass_M203GrenadeProjectile extends M203GrenadeProjectile; |
||||||
|
|
||||||
|
function TakeDamage( |
||||||
|
int damage, |
||||||
|
Pawn instigatedBy, |
||||||
|
Vector hitLocation, |
||||||
|
Vector momentum, |
||||||
|
class<DamageType> damageType, |
||||||
|
optional int hitIndex) |
||||||
|
{ |
||||||
|
local bool canTakeThisDamage; |
||||||
|
if (damageType == class'SirenScreamDamage') |
||||||
|
{ |
||||||
|
Disintegrate(hitLocation, Vect(0, 0, 1)); |
||||||
|
return; |
||||||
|
} |
||||||
|
canTakeThisDamage = |
||||||
|
(instigatedBy == instigator) |
||||||
|
|| class'FixProjectileFF'.static.IsFriendlyFireAcceptable(); |
||||||
|
if (canTakeThisDamage && !bDud) { |
||||||
|
Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
RemoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
/** |
||||||
|
* A helper class for `FixProjectileFF` that adds an instigator and |
||||||
|
* friendly fire checks to `TakeDamage()` method to avoid exploding projectiles |
||||||
|
* when not expected. |
||||||
|
* 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 FixProjectileFFClass_M32GrenadeProjectile extends M32GrenadeProjectile; |
||||||
|
|
||||||
|
function TakeDamage( |
||||||
|
int damage, |
||||||
|
Pawn instigatedBy, |
||||||
|
Vector hitLocation, |
||||||
|
Vector momentum, |
||||||
|
class<DamageType> damageType, |
||||||
|
optional int hitIndex) |
||||||
|
{ |
||||||
|
local bool canTakeThisDamage; |
||||||
|
if (damageType == class'SirenScreamDamage') |
||||||
|
{ |
||||||
|
Disintegrate(hitLocation, Vect(0, 0, 1)); |
||||||
|
return; |
||||||
|
} |
||||||
|
canTakeThisDamage = |
||||||
|
(instigatedBy == instigator) |
||||||
|
|| class'FixProjectileFF'.static.IsFriendlyFireAcceptable(); |
||||||
|
if (canTakeThisDamage && !bDud) { |
||||||
|
Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
RemoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
/** |
||||||
|
* A helper class for `FixProjectileFF` that adds an instigator and |
||||||
|
* friendly fire checks to `TakeDamage()` method to avoid exploding projectiles |
||||||
|
* when not expected. |
||||||
|
* 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 FixProjectileFFClass_M79GrenadeProjectile extends M79GrenadeProjectile; |
||||||
|
|
||||||
|
function TakeDamage( |
||||||
|
int damage, |
||||||
|
Pawn instigatedBy, |
||||||
|
Vector hitLocation, |
||||||
|
Vector momentum, |
||||||
|
class<DamageType> damageType, |
||||||
|
optional int hitIndex) |
||||||
|
{ |
||||||
|
local bool canTakeThisDamage; |
||||||
|
if (damageType == class'SirenScreamDamage') |
||||||
|
{ |
||||||
|
Disintegrate(hitLocation, Vect(0, 0, 1)); |
||||||
|
return; |
||||||
|
} |
||||||
|
canTakeThisDamage = |
||||||
|
(instigatedBy == instigator) |
||||||
|
|| class'FixProjectileFF'.static.IsFriendlyFireAcceptable(); |
||||||
|
if (canTakeThisDamage && !bDud) { |
||||||
|
Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
RemoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,95 @@ |
|||||||
|
/** |
||||||
|
* A helper class for `FixProjectileFF` that adds an instigator and |
||||||
|
* friendly fire checks to `TakeDamage()` method to avoid exploding projectiles |
||||||
|
* when not expected. |
||||||
|
* 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 FixProjectileFFClass_SPGrenadeProjectile extends SPGrenadeProjectile; |
||||||
|
|
||||||
|
var private SPGrenadeProjectile projectileFace; |
||||||
|
|
||||||
|
public final function SetFace(SPGrenadeProjectile newProjectileFace) |
||||||
|
{ |
||||||
|
projectileFace = newProjectileFace; |
||||||
|
} |
||||||
|
|
||||||
|
public function TakeDamage( |
||||||
|
int damage, |
||||||
|
Pawn instigatedBy, |
||||||
|
Vector hitLocation, |
||||||
|
Vector momentum, |
||||||
|
class<DamageType> damageType, |
||||||
|
optional int hitIndex) |
||||||
|
{ |
||||||
|
local bool canTakeThisDamage; |
||||||
|
if (damageType == class'SirenScreamDamage') |
||||||
|
{ |
||||||
|
Disintegrate(hitLocation, Vect(0, 0, 1)); |
||||||
|
return; |
||||||
|
} |
||||||
|
canTakeThisDamage = |
||||||
|
(instigatedBy == instigator) |
||||||
|
|| class'FixProjectileFF'.static.IsFriendlyFireAcceptable(); |
||||||
|
// Unlike M79/M32 - no `!bDud` check, since it's supposed to fall down |
||||||
|
if (canTakeThisDamage) |
||||||
|
{ |
||||||
|
Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
if (projectileFace != none) { |
||||||
|
projectileFace.Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
simulated function Explode(vector hitLocation, vector HitNormal) |
||||||
|
{ |
||||||
|
super.Explode(hitLocation, hitNormal); |
||||||
|
if (projectileFace != none) { |
||||||
|
projectileFace.Explode(hitLocation, hitNormal); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
simulated function Disintegrate(vector hitLocation, vector hitNormal) |
||||||
|
{ |
||||||
|
super.Disintegrate(hitLocation, hitNormal); |
||||||
|
if (projectileFace != none) { |
||||||
|
projectileFace.Disintegrate(hitLocation, hitNormal); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
event Tick(float delta) |
||||||
|
{ |
||||||
|
super.Tick(delta); |
||||||
|
if (projectileFace != none) |
||||||
|
{ |
||||||
|
projectileFace.SetLocation(location); |
||||||
|
projectileFace.SetRotation(rotation); |
||||||
|
projectileFace.velocity = velocity; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
event OnDestroyed() |
||||||
|
{ |
||||||
|
if (projectileFace != none) { |
||||||
|
projectileFace.Destroy(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
RemoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,107 @@ |
|||||||
|
/** |
||||||
|
* A helper class for `FixProjectileFF` that adds an instigator and |
||||||
|
* friendly fire checks to `TakeDamage()` method to avoid exploding projectiles |
||||||
|
* when not expected. |
||||||
|
* 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 FixProjectileFFClass_SealSquealProjectile extends SealSquealProjectile; |
||||||
|
|
||||||
|
var private SealSquealProjectile projectileFace; |
||||||
|
|
||||||
|
public final function SetFace(SealSquealProjectile newProjectileFace) |
||||||
|
{ |
||||||
|
projectileFace = newProjectileFace; |
||||||
|
} |
||||||
|
|
||||||
|
public function TakeDamage( |
||||||
|
int damage, |
||||||
|
Pawn instigatedBy, |
||||||
|
Vector hitLocation, |
||||||
|
Vector momentum, |
||||||
|
class<DamageType> damageType, |
||||||
|
optional int hitIndex) |
||||||
|
{ |
||||||
|
local bool canTakeThisDamage; |
||||||
|
if (damageType == class'SirenScreamDamage') |
||||||
|
{ |
||||||
|
Disintegrate(hitLocation, Vect(0, 0, 1)); |
||||||
|
return; |
||||||
|
} |
||||||
|
canTakeThisDamage = |
||||||
|
(instigatedBy == instigator) |
||||||
|
|| class'FixProjectileFF'.static.IsFriendlyFireAcceptable(); |
||||||
|
// Unlike M79/M32 - no `!bDud` check, since it's supposed to fall down |
||||||
|
if (canTakeThisDamage) |
||||||
|
{ |
||||||
|
Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
if (projectileFace != none) { |
||||||
|
projectileFace.Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
simulated function Explode(vector hitLocation, vector HitNormal) |
||||||
|
{ |
||||||
|
super.Explode(hitLocation, hitNormal); |
||||||
|
if (projectileFace != none) { |
||||||
|
projectileFace.Explode(hitLocation, hitNormal); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
simulated function Disintegrate(vector hitLocation, vector hitNormal) |
||||||
|
{ |
||||||
|
super.Disintegrate(hitLocation, hitNormal); |
||||||
|
if (projectileFace != none) { |
||||||
|
projectileFace.Disintegrate(hitLocation, hitNormal); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
simulated function Stick(Actor hitActor, vector hitLocation) |
||||||
|
{ |
||||||
|
super.Stick(hitActor, hitLocation); |
||||||
|
if (projectileFace != none) |
||||||
|
{ |
||||||
|
projectileFace.SetCollision(true, true); |
||||||
|
projectileFace.SetLocation(location); |
||||||
|
projectileFace.SetRotation(rotation); |
||||||
|
projectileFace.Stick(hitActor, hitLocation); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
event Tick(float delta) |
||||||
|
{ |
||||||
|
super.Tick(delta); |
||||||
|
if (projectileFace == none) return; |
||||||
|
if (projectileFace.bStuck) return; |
||||||
|
|
||||||
|
projectileFace.SetLocation(location); |
||||||
|
projectileFace.SetRotation(rotation); |
||||||
|
projectileFace.velocity = velocity; |
||||||
|
} |
||||||
|
|
||||||
|
event OnDestroyed() |
||||||
|
{ |
||||||
|
if (projectileFace != none) { |
||||||
|
projectileFace.Destroy(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
RemoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
/** |
||||||
|
* A helper class for `FixProjectileFF` that adds an instigator and |
||||||
|
* friendly fire checks to `TakeDamage()` method to avoid exploding projectiles |
||||||
|
* when not expected. |
||||||
|
* 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 FixProjectileFFClass_ZEDGunProjectile extends ZEDGunProjectile; |
||||||
|
|
||||||
|
function TakeDamage( |
||||||
|
int damage, |
||||||
|
Pawn instigatedBy, |
||||||
|
Vector hitLocation, |
||||||
|
Vector momentum, |
||||||
|
class<DamageType> damageType, |
||||||
|
optional int hitIndex) |
||||||
|
{ |
||||||
|
local bool canTakeThisDamage; |
||||||
|
if (damageType == class'SirenScreamDamage') |
||||||
|
{ |
||||||
|
Disintegrate(hitLocation, Vect(0, 0, 1)); |
||||||
|
return; |
||||||
|
} |
||||||
|
canTakeThisDamage = |
||||||
|
(instigatedBy == instigator) |
||||||
|
|| class'FixProjectileFF'.static.IsFriendlyFireAcceptable(); |
||||||
|
if (canTakeThisDamage && !bDud) { |
||||||
|
Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
RemoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
/** |
||||||
|
* A helper class for `FixProjectileFF` that adds an instigator and |
||||||
|
* friendly fire checks to `TakeDamage()` method to avoid exploding projectiles |
||||||
|
* when not expected. |
||||||
|
* 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 FixProjectileFFClass_ZEDMKIIPrimaryProjectile |
||||||
|
extends ZEDMKIIPrimaryProjectile; |
||||||
|
|
||||||
|
function TakeDamage( |
||||||
|
int damage, |
||||||
|
Pawn instigatedBy, |
||||||
|
Vector hitLocation, |
||||||
|
Vector momentum, |
||||||
|
class<DamageType> damageType, |
||||||
|
optional int hitIndex) |
||||||
|
{ |
||||||
|
local bool canTakeThisDamage; |
||||||
|
if (damageType == class'SirenScreamDamage') |
||||||
|
{ |
||||||
|
Disintegrate(hitLocation, Vect(0, 0, 1)); |
||||||
|
return; |
||||||
|
} |
||||||
|
canTakeThisDamage = |
||||||
|
(instigatedBy == instigator) |
||||||
|
|| class'FixProjectileFF'.static.IsFriendlyFireAcceptable(); |
||||||
|
if (canTakeThisDamage && !bDud) { |
||||||
|
Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
RemoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
/** |
||||||
|
* A helper class for `FixProjectileFF` that adds an instigator and |
||||||
|
* friendly fire checks to `TakeDamage()` method to avoid exploding projectiles |
||||||
|
* when not expected. |
||||||
|
* 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 FixProjectileFFClass_ZEDMKIISecondaryProjectile extends ZEDGunProjectile; |
||||||
|
|
||||||
|
function TakeDamage( |
||||||
|
int damage, |
||||||
|
Pawn instigatedBy, |
||||||
|
Vector hitLocation, |
||||||
|
Vector momentum, |
||||||
|
class<DamageType> damageType, |
||||||
|
optional int hitIndex) |
||||||
|
{ |
||||||
|
local bool canTakeThisDamage; |
||||||
|
if (damageType == class'SirenScreamDamage') |
||||||
|
{ |
||||||
|
Disintegrate(hitLocation, Vect(0, 0, 1)); |
||||||
|
return; |
||||||
|
} |
||||||
|
canTakeThisDamage = |
||||||
|
(instigatedBy == instigator) |
||||||
|
|| class'FixProjectileFF'.static.IsFriendlyFireAcceptable(); |
||||||
|
if (canTakeThisDamage && !bDud) { |
||||||
|
Explode(hitLocation, Vect(0, 0, 0)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
RemoteRole = ROLE_None |
||||||
|
} |
@ -0,0 +1,167 @@ |
|||||||
|
/** |
||||||
|
* Overloaded mutator events listener to replace projectiles vulnerable to |
||||||
|
* friendly fire. |
||||||
|
* 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_FixProjectileFF extends MutatorListenerBase |
||||||
|
abstract; |
||||||
|
|
||||||
|
static function bool CheckReplacement(Actor other, out byte isSuperRelevant) |
||||||
|
{ |
||||||
|
local ROBallisticProjectile projectile; |
||||||
|
local class<ROBallisticProjectile> newClass; |
||||||
|
projectile = ROBallisticProjectile(other); |
||||||
|
if (projectile == none) return true; |
||||||
|
|
||||||
|
projectile.bGameRelevant = true; |
||||||
|
newClass = |
||||||
|
class'FixProjectileFF'.static.FindFixedClass(projectile.class); |
||||||
|
if (newClass == none || newClass == projectile.class) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
ReplaceProjectileWith(ROBallisticProjectile(other), newClass); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
static function ReplaceProjectileWith( |
||||||
|
ROBallisticProjectile oldProjectile, |
||||||
|
class<ROBallisticProjectile> replacementClass) |
||||||
|
{ |
||||||
|
local Pawn instigator; |
||||||
|
local ROBallisticProjectile newProjectile; |
||||||
|
if (oldProjectile == none) return; |
||||||
|
if (replacementClass == none) return; |
||||||
|
instigator = oldProjectile.instigator; |
||||||
|
if (instigator == none) return; |
||||||
|
newProjectile = instigator.Spawn( replacementClass,,, |
||||||
|
oldProjectile.location, |
||||||
|
oldProjectile.rotation); |
||||||
|
if (newProjectile == none) return; |
||||||
|
|
||||||
|
// Move projectile damage data to make sure new projectile deals |
||||||
|
// exactly the same damage as the old one and the old one no longer |
||||||
|
// deals any. |
||||||
|
// Technically we only need to zero `oldProjectile` damage values for |
||||||
|
// most weapons, since they are automatically the same for the |
||||||
|
// new projectile. |
||||||
|
// However `KFMod.HuskGun` is an exception that changes these values |
||||||
|
// depending of the charge, so we need to either consider this as a |
||||||
|
// special case or just move valkues all the time - which is what we have |
||||||
|
// chosen to do. |
||||||
|
newProjectile.damage = oldProjectile.damage; |
||||||
|
oldProjectile.damage = 0; |
||||||
|
newProjectile.damageRadius = oldProjectile.damageRadius; |
||||||
|
oldProjectile.damageRadius = 0; |
||||||
|
MoveImpactDamage(oldProjectile, newProjectile); |
||||||
|
// New projectile must govern all of the mechanics, so make the old mimick |
||||||
|
// former's movements as close as possible |
||||||
|
SetupOldProjectileAsFace(oldProjectile, newProjectile); |
||||||
|
} |
||||||
|
|
||||||
|
// Make old projectile behave as close to the new one as possible |
||||||
|
static function SetupOldProjectileAsFace( |
||||||
|
ROBallisticProjectile oldProjectile, |
||||||
|
ROBallisticProjectile newProjectile) |
||||||
|
{ |
||||||
|
local FixProjectileFFClass_SPGrenadeProjectile newProjectileAsOrca; |
||||||
|
local FixProjectileFFClass_SealSquealProjectile newProjectileAsHarpoon; |
||||||
|
if (oldProjectile == none) return; |
||||||
|
if (newProjectile == none) return; |
||||||
|
|
||||||
|
// Removing collisions: |
||||||
|
// 1. Avoids unnecessary bumping into zeds and other `Actor`s; |
||||||
|
// 2. Removes `oldProjectile` from being accessible by |
||||||
|
// `VisibleCollidingActors()`, therefore avoiding unwanted |
||||||
|
// `TakeDamage()` calls from friendly fire. |
||||||
|
oldProjectile.SetCollision(false, false); |
||||||
|
// Prevent `oldProjectile` from dealing explosion damage in case something |
||||||
|
// still damages it |
||||||
|
oldProjectile.bHurtEntry = true; |
||||||
|
// We can only make client-side projectile follow server-side one for |
||||||
|
// two projectile classes |
||||||
|
newProjectileAsOrca = |
||||||
|
FixProjectileFFClass_SPGrenadeProjectile(newProjectile); |
||||||
|
if (newProjectileAsOrca != none) |
||||||
|
{ |
||||||
|
newProjectileAsOrca.SetFace(SPGrenadeProjectile(oldProjectile)); |
||||||
|
return; |
||||||
|
} |
||||||
|
newProjectileAsHarpoon = |
||||||
|
FixProjectileFFClass_SealSquealProjectile(newProjectile); |
||||||
|
if (newProjectileAsHarpoon != none) { |
||||||
|
newProjectileAsHarpoon.SetFace(SealSquealProjectile(oldProjectile)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// `impactDamage` is sepratly defined in 4 different base classes of interest |
||||||
|
// and moving (or zeroing) it is cumbersome enough to warrant a separate method |
||||||
|
static function MoveImpactDamage( |
||||||
|
ROBallisticProjectile oldProjectile, |
||||||
|
ROBallisticProjectile newProjectile) |
||||||
|
{ |
||||||
|
local LAWProj oldProjectileAsLaw, newProjectileAsLaw; |
||||||
|
local M79GrenadeProjectile oldProjectileAsM79, newProjectileAsM79; |
||||||
|
local SPGrenadeProjectile oldProjectileAsOrca, newProjectileAsOrca; |
||||||
|
local SealSquealProjectile oldProjectileAsHarpoon, newProjectileAsHarpoon; |
||||||
|
if (oldProjectile == none) return; |
||||||
|
if (newProjectile == none) return; |
||||||
|
|
||||||
|
// L.A.W + derivatives: |
||||||
|
// Zed guns, Flare Revolver, Husk Gun |
||||||
|
oldProjectileAsLaw = LawProj(oldProjectile); |
||||||
|
newProjectileAsLaw = LawProj(newProjectile); |
||||||
|
if (oldProjectileAsLaw != none && newProjectileAsLaw != none) |
||||||
|
{ |
||||||
|
newProjectileAsLaw.impactDamage = oldProjectileAsLaw.impactDamage; |
||||||
|
oldProjectileAsLaw.impactDamage = 0; |
||||||
|
return; |
||||||
|
} |
||||||
|
// M79 Grenade Launcher + derivatives: |
||||||
|
// M32, M4 203 |
||||||
|
oldProjectileAsM79 = M79GrenadeProjectile(oldProjectile); |
||||||
|
newProjectileAsM79 = M79GrenadeProjectile(newProjectile); |
||||||
|
if (oldProjectileAsM79 != none && newProjectileAsM79 != none) |
||||||
|
{ |
||||||
|
newProjectileAsM79.impactDamage = oldProjectileAsM79.impactDamage; |
||||||
|
oldProjectileAsM79.impactDamage = 0; |
||||||
|
return; |
||||||
|
} |
||||||
|
// The Orca Bomb Propeller |
||||||
|
oldProjectileAsOrca = SPGrenadeProjectile(oldProjectile); |
||||||
|
newProjectileAsOrca = SPGrenadeProjectile(newProjectile); |
||||||
|
if (oldProjectileAsOrca != none && newProjectileAsOrca != none) |
||||||
|
{ |
||||||
|
newProjectileAsOrca.impactDamage = oldProjectileAsOrca.impactDamage; |
||||||
|
oldProjectileAsOrca.impactDamage = 0; |
||||||
|
return; |
||||||
|
} |
||||||
|
// SealSqueal Harpoon Bomber |
||||||
|
oldProjectileAsHarpoon = SealSquealProjectile(oldProjectile); |
||||||
|
newProjectileAsHarpoon = SealSquealProjectile(newProjectile); |
||||||
|
if (oldProjectileAsHarpoon != none && newProjectileAsHarpoon != none) |
||||||
|
{ |
||||||
|
newProjectileAsHarpoon.impactDamage = oldProjectileAsHarpoon.impactDamage; |
||||||
|
oldProjectileAsHarpoon.impactDamage = 0; |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedEvents = class'MutatorEvents' |
||||||
|
} |
Loading…
Reference in new issue