diff --git a/config/AcediaFixes.ini b/config/AcediaFixes.ini index 639ded2..7acc9b9 100644 --- a/config/AcediaFixes.ini +++ b/config/AcediaFixes.ini @@ -218,6 +218,16 @@ alwaysScale=Class'KFMod.DamTypeZEDGunMKII' ; Damage types, for which we should never reaply friendly fire scaling. ;neverScale=Class'KFMod.???' +[AcediaFixes.FixProjectileFF] +; 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). +autoEnable=true +; 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. +ignoreFriendlyFire=false [AcediaFixes.FixZedTimeLags] ; When zed time activates, game speed is immediately set to diff --git a/sources/FixProjectileFF/FixProjectileFF.uc b/sources/FixProjectileFF/FixProjectileFF.uc new file mode 100644 index 0000000..6e964ea --- /dev/null +++ b/sources/FixProjectileFF/FixProjectileFF.uc @@ -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 . + */ +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 vulnerableClass; + var class protectedClass; +}; +var private const array 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 FindFixedClass( + class projectileClass) +{ + local int i; + local array 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' +} \ No newline at end of file diff --git a/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_FlareRevolverProjectile.uc b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_FlareRevolverProjectile.uc new file mode 100644 index 0000000..46b8831 --- /dev/null +++ b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_FlareRevolverProjectile.uc @@ -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 . + */ +class FixProjectileFFClass_FlareRevolverProjectile + extends FlareRevolverProjectile; + +function TakeDamage( + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class 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 +} \ No newline at end of file diff --git a/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_HuskGunProjectile.uc b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_HuskGunProjectile.uc new file mode 100644 index 0000000..f79d77d --- /dev/null +++ b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_HuskGunProjectile.uc @@ -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 . + */ +class FixProjectileFFClass_HuskGunProjectile extends HuskGunProjectile; + +function TakeDamage( + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class 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 +} \ No newline at end of file diff --git a/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_LAWProj.uc b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_LAWProj.uc new file mode 100644 index 0000000..d823df2 --- /dev/null +++ b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_LAWProj.uc @@ -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 . + */ +class FixProjectileFFClass_LAWProj extends LAWProj; + +function TakeDamage( + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class 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 +} \ No newline at end of file diff --git a/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_M203GrenadeProjectile.uc b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_M203GrenadeProjectile.uc new file mode 100644 index 0000000..5664c19 --- /dev/null +++ b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_M203GrenadeProjectile.uc @@ -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 . + */ +class FixProjectileFFClass_M203GrenadeProjectile extends M203GrenadeProjectile; + +function TakeDamage( + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class 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 +} \ No newline at end of file diff --git a/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_M32GrenadeProjectile.uc b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_M32GrenadeProjectile.uc new file mode 100644 index 0000000..8c5179c --- /dev/null +++ b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_M32GrenadeProjectile.uc @@ -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 . + */ +class FixProjectileFFClass_M32GrenadeProjectile extends M32GrenadeProjectile; + +function TakeDamage( + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class 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 +} \ No newline at end of file diff --git a/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_M79GrenadeProjectile.uc b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_M79GrenadeProjectile.uc new file mode 100644 index 0000000..9eaa34b --- /dev/null +++ b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_M79GrenadeProjectile.uc @@ -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 . + */ +class FixProjectileFFClass_M79GrenadeProjectile extends M79GrenadeProjectile; + +function TakeDamage( + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class 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 +} \ No newline at end of file diff --git a/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_SPGrenadeProjectile.uc b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_SPGrenadeProjectile.uc new file mode 100644 index 0000000..f4b575c --- /dev/null +++ b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_SPGrenadeProjectile.uc @@ -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 . + */ +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, + 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 +} \ No newline at end of file diff --git a/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_SealSquealProjectile.uc b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_SealSquealProjectile.uc new file mode 100644 index 0000000..0799922 --- /dev/null +++ b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_SealSquealProjectile.uc @@ -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 . + */ +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, + 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 +} \ No newline at end of file diff --git a/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_ZEDGunProjectile.uc b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_ZEDGunProjectile.uc new file mode 100644 index 0000000..2beb6bc --- /dev/null +++ b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_ZEDGunProjectile.uc @@ -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 . + */ +class FixProjectileFFClass_ZEDGunProjectile extends ZEDGunProjectile; + +function TakeDamage( + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class 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 +} \ No newline at end of file diff --git a/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_ZEDMKIIPrimaryProjectile.uc b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_ZEDMKIIPrimaryProjectile.uc new file mode 100644 index 0000000..a9ec190 --- /dev/null +++ b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_ZEDMKIIPrimaryProjectile.uc @@ -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 . + */ +class FixProjectileFFClass_ZEDMKIIPrimaryProjectile + extends ZEDMKIIPrimaryProjectile; + +function TakeDamage( + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class 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 +} \ No newline at end of file diff --git a/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_ZEDMKIISecondaryProjectile.uc b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_ZEDMKIISecondaryProjectile.uc new file mode 100644 index 0000000..673806e --- /dev/null +++ b/sources/FixProjectileFF/FixedClasses/FixProjectileFFClass_ZEDMKIISecondaryProjectile.uc @@ -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 . + */ +class FixProjectileFFClass_ZEDMKIISecondaryProjectile extends ZEDGunProjectile; + +function TakeDamage( + int damage, + Pawn instigatedBy, + Vector hitLocation, + Vector momentum, + class 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 +} \ No newline at end of file diff --git a/sources/FixProjectileFF/MutatorListener_FixProjectileFF.uc b/sources/FixProjectileFF/MutatorListener_FixProjectileFF.uc new file mode 100644 index 0000000..a36eca3 --- /dev/null +++ b/sources/FixProjectileFF/MutatorListener_FixProjectileFF.uc @@ -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 . + */ +class MutatorListener_FixProjectileFF extends MutatorListenerBase + abstract; + +static function bool CheckReplacement(Actor other, out byte isSuperRelevant) +{ + local ROBallisticProjectile projectile; + local class 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 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' +} \ No newline at end of file diff --git a/sources/Manifest.uc b/sources/Manifest.uc index 4276afb..b17c834 100644 --- a/sources/Manifest.uc +++ b/sources/Manifest.uc @@ -1,6 +1,6 @@ /** * Manifest for AcediaFixes package - * Copyright 2020 Anton Tarasenko + * Copyright 2020 - 2021 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -30,4 +30,5 @@ defaultproperties features(5) = class'FixSpectatorCrash' features(6) = class'FixDualiesCost' features(7) = class'FixInventoryAbuse' + features(8) = class'FixProjectileFF' } \ No newline at end of file