Browse Source

Change all `Feature`s to adapt to new `AcediaCore`

master
Anton Tarasenko 4 years ago
parent
commit
00100dd4e7
  1. 7
      config/AcediaFixes.ini
  2. 67
      sources/FixAmmoSelling/FixAmmoSelling.uc
  3. 72
      sources/FixAmmoSelling/FixAmmoSellingService.uc
  4. 82
      sources/FixDoshSpam/FixDoshSpam.uc
  5. 44
      sources/FixDualiesCost/DualiesCostRule.uc
  6. 118
      sources/FixDualiesCost/FixDualiesCost.uc
  7. 75
      sources/FixFFHack/FFHackRule.uc
  8. 46
      sources/FixFFHack/FixFFHack.uc
  9. 59
      sources/FixInfiniteNades/FixInfiniteNades.uc
  10. 3
      sources/FixInfiniteNades/FixedFragFire.uc
  11. 3
      sources/FixInfiniteNades/MutatorListener_FixInfiniteNades.uc
  12. 14
      sources/FixInventoryAbuse/FixInventoryAbuse.uc
  13. 67
      sources/FixLogSpam/SpamPickup/HelperPickup.uc
  14. 38
      sources/FixLogSpam/SpamPickup/PickupSpamRule.uc
  15. 48
      sources/FixLogSpam/SpamTrader/HelperTrader.uc
  16. 110
      sources/FixPipes/FixPipes.uc
  17. 8
      sources/FixProjectileFF/FixProjectileFF.uc
  18. 3
      sources/FixProjectileFF/MutatorListener_FixProjectileFF.uc
  19. 46
      sources/FixSpectatorCrash/FixSpectatorCrash.uc
  20. 47
      sources/FixZedTimeLags/FixZedTimeLags.uc

7
config/AcediaFixes.ini

@ -133,6 +133,13 @@ doshPerSecondLimitMin=5
; We use `doshPerSecondLimitMax` when there's no dosh on the map and
; scale linearly between them as it's amount grows.
criticalDoshAmount=25
; We immediately reduce the rate dosh can be spawned at when players throw
; new wads of cash. But, for performance reasons, we only periodically turn it
; back up. This interval determines how often we check for whether it's okay
; to raise the limit on the spawned dosh.
; You should not set this value too high, it is recommended not to exceed
; 1 second.
checkInterval=0.25
[AcediaFixes.FixSpectatorCrash]

67
sources/FixAmmoSelling/FixAmmoSelling.uc

@ -131,13 +131,12 @@ struct WeaponRecord
var int lastAmmoAmount;
};
// All weapons we've detected so far.
var private array<WeaponRecord> registeredWeapons;
protected function OnEnabled()
{
local LevelInfo level;
local KFWeapon nextWeapon;
local KFAmmoPickup nextPickup;
level = _.unreal.GetLevel();
// Find all abusable weapons
foreach level.DynamicActors(class'KFMod.KFWeapon', nextWeapon) {
FixWeapon(nextWeapon);
@ -151,8 +150,12 @@ protected function OnEnabled()
protected function OnDisabled()
{
local int i;
local LevelInfo level;
local AmmoPickupStalker nextStalker;
local array<AmmoPickupStalker> stalkers;
local array<WeaponRecord> registeredWeapons;
level = _.unreal.GetLevel();
registeredWeapons = FixAmmoSellingService(GetService()).registeredWeapons;
// Restore all the `pickupClass` variables we've changed.
for (i = 0; i < registeredWeapons.length; i += 1)
{
@ -162,7 +165,6 @@ protected function OnDisabled()
registeredWeapons[i].weapon.default.pickupClass;
}
}
registeredWeapons.length = 0;
// Kill all the stalkers;
// to be safe, avoid destroying them directly in the iterator.
foreach level.DynamicActors(class'AmmoPickupStalker', nextStalker) {
@ -194,10 +196,14 @@ public static final function bool IsReplacer(class<Actor> pickupClass)
// 2. Starts tracking abusable weapon to detect when player buys ammo for it.
public final function FixWeapon(KFWeapon potentialAbuser)
{
local int i;
local WeaponRecord newRecord;
local int i;
local WeaponRecord newRecord;
local array<WeaponRecord> registeredWeapons;
local FixAmmoSellingService service;
if (potentialAbuser == none) return;
service = FixAmmoSellingService(GetService());
registeredWeapons = service.registeredWeapons;
for (i = 0; i < registeredWeapons.length; i += 1)
{
if (registeredWeapons[i].weapon == potentialAbuser) {
@ -211,13 +217,14 @@ public final function FixWeapon(KFWeapon potentialAbuser)
potentialAbuser.pickupClass = rules[i].pickupReplacement;
newRecord.weapon = potentialAbuser;
registeredWeapons[registeredWeapons.length] = newRecord;
service.registeredWeapons = registeredWeapons;
return;
}
}
}
// Finds ammo instance for recorded weapon in it's owner's inventory.
private final function WeaponRecord FindAmmoInstance(WeaponRecord record)
public final function WeaponRecord FindAmmoInstance(WeaponRecord record)
{
local Inventory invIter;
local KFAmmunition ammo;
@ -285,7 +292,7 @@ private final function float GetPriceCorrection(
// Takes current ammo and last recorded in `record` value to calculate
// how much money to take from the player
// (calculations are done via `GetPriceCorrection()`).
private final function WeaponRecord TaxAmmoChange(WeaponRecord record)
public final function WeaponRecord TaxAmmoChange(WeaponRecord record)
{
local int ammoDiff;
local KFPawn taxPayer;
@ -322,16 +329,20 @@ private final function WeaponRecord TaxAmmoChange(WeaponRecord record)
// to avoid charging his for it.
public final function RecordAmmoPickup(Pawn pawnWithAmmo, KFAmmoPickup pickup)
{
local int i;
local int newAmount;
local int i;
local int newAmount;
local array<WeaponRecord> registeredWeapons;
local FixAmmoSellingService service;
// Check conditions from `KFAmmoPickup` code (`Touch()` method)
if (pickup == none) return;
if (pawnWithAmmo == none) return;
if (pawnWithAmmo.controller == none) return;
if (!pawnWithAmmo.bCanPickupInventory) return;
if (!FastTrace(pawnWithAmmo.location, pickup.location)) return;
if (pickup == none) return;
if (pawnWithAmmo == none) return;
if (pawnWithAmmo.controller == none) return;
if (!pawnWithAmmo.bCanPickupInventory) return;
if (!pickup.FastTrace(pawnWithAmmo.location, pickup.location)) return;
// Add relevant amount of ammo to our records
service = FixAmmoSellingService(GetService());
registeredWeapons = service.registeredWeapons;
for (i = 0; i < registeredWeapons.length; i += 1)
{
if (registeredWeapons[i].weapon == none) continue;
@ -343,29 +354,7 @@ public final function RecordAmmoPickup(Pawn pawnWithAmmo, KFAmmoPickup pickup)
registeredWeapons[i].lastAmmoAmount = newAmount;
}
}
}
event Tick(float delta)
{
local int i;
// For all the weapon records...
i = 0;
while (i < registeredWeapons.length)
{
// ...remove dead records
if (registeredWeapons[i].weapon == none)
{
registeredWeapons.Remove(i, 1);
continue;
}
// ...find ammo if it's missing
if (registeredWeapons[i].ammo == none) {
registeredWeapons[i] = FindAmmoInstance(registeredWeapons[i]);
}
// ...tax for ammo, if we can
registeredWeapons[i] = TaxAmmoChange(registeredWeapons[i]);
i += 1;
}
service.registeredWeapons = registeredWeapons;
}
defaultproperties
@ -383,4 +372,6 @@ defaultproperties
rules(9)=(abusableWeapon=class'KFMod.SeekerSixRocketLauncher',pickupReplacement=class'FixAmmoSellingClass_SeekerSixPickup')
// Listeners
requiredListeners(0) = class'MutatorListener_FixAmmoSelling'
// Service
serviceClass = class'FixAmmoSellingService'
}

72
sources/FixAmmoSelling/FixAmmoSellingService.uc

@ -0,0 +1,72 @@
/**
* Service for storing `FixAmmoSelling`'s `Actor`s.
* 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 FixAmmoSellingService extends FeatureService
dependson(FixAmmoSelling);
var private FixAmmoSelling ammoSellingFix;
// All weapons we've detected so far.
// Made `public` to avoid needless calls, since this is not part of
// a library's interface anyway.
var public array<FixAmmoSelling.WeaponRecord> registeredWeapons;
protected function Finalizer()
{
ammoSellingFix = none;
}
public function SetOwnerFeature(Feature newOwnerFeature)
{
super.SetOwnerFeature(newOwnerFeature);
ammoSellingFix = FixAmmoSelling(newOwnerFeature);
}
event Tick(float delta)
{
local int i;
if (ammoSellingFix == none) {
return;
}
// For all the weapon records...
i = 0;
while (i < registeredWeapons.length)
{
// ...remove dead records
if (registeredWeapons[i].weapon == none)
{
registeredWeapons.Remove(i, 1);
continue;
}
// ...find ammo if it's missing
if (registeredWeapons[i].ammo == none)
{
registeredWeapons[i] =
ammoSellingFix.FindAmmoInstance(registeredWeapons[i]);
}
// ...tax for ammo, if we can
registeredWeapons[i] =
ammoSellingFix.TaxAmmoChange(registeredWeapons[i]);
i += 1;
}
}
defaultproperties
{
}

82
sources/FixDoshSpam/FixDoshSpam.uc

@ -8,7 +8,7 @@
* It fixes them by limiting speed, with which dosh can spawn, and
* allowing this limit to decrease when there's already too much dosh
* present on the map.
* Copyright 2019 Anton Tarasenko
* Copyright 2019 - 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@ -71,39 +71,56 @@ var private config const float doshPerSecondLimitMin;
// scale linearly between them as it's amount grows.
var private config const int criticalDoshAmount;
// To limit dosh spawning speed we need some measure of
// time passage between ticks.
// This variable stores last value seen by us as a good approximation.
// It's a real (not in-game) time.
var private float lastTickDuration;
// We immediately reduce the rate dosh can be spawned at when players throw
// new wads of cash. But, for performance reasons, we only periodically turn it
// back up. This interval determines how often we check for whether it's okay
// to raise the limit on the spawned dosh.
// You should not set this value too high, it is recommended not to exceed
// 1 second.
var private config const float checkInterval;
// This structure records how much a certain player has
// contributed to an overall dosh creation.
struct DoshStreamPerPlayer
{
var PlayerController player;
// Reference to `PlayerController`
var NativeActorRef player;
// Amount of dosh we remember this player creating, decays with time.
var float contribution;
var float contribution;
};
var private array<DoshStreamPerPlayer> currentContributors;
// Wads of cash that are lying around on the map.
var private array<CashPickup> wads;
var private array<NativeActorRef> wads;
// Generates "reset" events when `wads` array is getting cleaned from
// destroyed/picked up dosh and players' contributions are reduced.
var private RealTimer checkTimer;
protected function OnEnabled()
{
local CashPickup nextCash;
local LevelInfo level;
local CashPickup nextCash;
checkTimer = _.time.StartRealTimer(checkInterval, true);
checkTimer.OnElapsed(self).connect = Tick;
level = _.unreal.GetLevel();
// Find all wads of cash laying around on the map,
// so that we could accordingly limit the cash spam.
foreach level.DynamicActors(class'KFMod.CashPickup', nextCash) {
wads[wads.length] = nextCash;
wads[wads.length] = _.unreal.ActorRef(nextCash);
}
}
protected function OnDisabled()
{
local int i;
_.memory.FreeMany(wads);
for (i = 0; i < currentContributors.length; i += 1) {
currentContributors[i].player.FreeSelf();
}
wads.length = 0;
currentContributors.length = 0;
checkTimer.FreeSelf();
}
// Did player with this controller contribute to the latest dosh generation?
@ -117,11 +134,13 @@ public final function bool IsDoshStreamOverLimit()
{
local int i;
local float overallContribution;
local float allowedContribution;
overallContribution = 0.0;
for (i = 0; i < currentContributors.length; i += 1) {
overallContribution += currentContributors[i].contribution;
}
return (overallContribution > lastTickDuration * GetCurrentDPSLimit());
allowedContribution = checkTimer.GetElapsedTime() * GetCurrentDPSLimit();
return overallContribution > allowedContribution;
}
// What is our current dosh per second limit?
@ -148,7 +167,7 @@ private final function int GetContributorIndex(PlayerController player)
for (i = 0; i < currentContributors.length; i += 1)
{
if (currentContributors[i].player == player) {
if (currentContributors[i].player.Get() == player) {
return i;
}
}
@ -157,20 +176,11 @@ private final function int GetContributorIndex(PlayerController player)
// Adds given cash to given player contribution record and
// registers that cash in our wads array.
// Does nothing if given cash was already registered.
public final function AddContribution(PlayerController player, CashPickup cash)
{
local int i;
local int playerIndex;
local DoshStreamPerPlayer newStreamRecord;
// Check if given dosh was already accounted for.
for (i = 0; i < wads.length; i += 1)
{
if (cash == wads[i]) {
return;
}
}
wads[wads.length] = cash;
wads[wads.length] = _.unreal.ActorRef(cash);
// Add contribution to player
playerIndex = GetContributorIndex(player);
if (playerIndex >= 0)
@ -178,16 +188,16 @@ public final function AddContribution(PlayerController player, CashPickup cash)
currentContributors[playerIndex].contribution += 1.0;
return;
}
newStreamRecord.player = player;
newStreamRecord.player = _.unreal.ActorRef(player);
newStreamRecord.contribution = 1.0;
currentContributors[currentContributors.length] = newStreamRecord;
}
private final function ReducePlayerContributions(float trueTimePassed)
private final function ReducePlayerContributions()
{
local int i;
local float streamReduction;
streamReduction = trueTimePassed *
streamReduction = checkInterval *
(GetCurrentDPSLimit() / currentContributors.length);
for (i = 0; i < currentContributors.length; i += 1) {
currentContributors[i].contribution -= streamReduction;
@ -201,7 +211,9 @@ private final function CleanWadsArray()
i = 0;
while (i < wads.length)
{
if (wads[i] == none) {
if (wads[i].Get() == none)
{
wads[i].FreeSelf();
wads.Remove(i, 1);
}
else {
@ -219,20 +231,21 @@ private final function RemoveNonContributors()
{
// We want to keep on record even players that quit,
// since their contribution still must be accounted for.
if (currentContributors[i].contribution <= 0.0) continue;
updContributors[updContributors.length] = currentContributors[i];
if (currentContributors[i].contribution <= 0.0) {
currentContributors[i].player.FreeSelf();
}
else {
updContributors[updContributors.length] = currentContributors[i];
}
}
currentContributors = updContributors;
}
event Tick(float delta)
private function Tick(Timer source)
{
local float trueTimePassed;
trueTimePassed = delta * (1.1 / level.timeDilation);
CleanWadsArray();
ReducePlayerContributions(trueTimePassed);
ReducePlayerContributions();
RemoveNonContributors();
lastTickDuration = trueTimePassed;
}
defaultproperties
@ -240,6 +253,7 @@ defaultproperties
doshPerSecondLimitMax = 50
doshPerSecondLimitMin = 5
criticalDoshAmount = 25
checkInterval = 0.25
// Listeners
requiredListeners(0) = class'MutatorListener_FixDoshSpam'
}

44
sources/FixDualiesCost/DualiesCostRule.uc

@ -1,44 +0,0 @@
/**
* This rule detects any pickup events to allow us to
* properly record and/or fix pistols' prices.
* Copyright 2019 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 DualiesCostRule extends GameRules;
function bool OverridePickupQuery(
Pawn other,
Pickup item,
out byte allowPickup
)
{
local KFWeaponPickup weaponPickup;
local FixDualiesCost dualiesCostFix;
weaponPickup = KFWeaponPickup(item);
dualiesCostFix = FixDualiesCost(class'FixDualiesCost'.static.GetInstance());
if (weaponPickup != none && dualiesCostFix != none)
{
dualiesCostFix.ApplyPendingValues();
dualiesCostFix.StoreSinglePistolValues();
dualiesCostFix.SetNextSellValue(weaponPickup.sellValue);
}
return super.OverridePickupQuery(other, item, allowPickup);
}
defaultproperties
{
}

118
sources/FixDualiesCost/FixDualiesCost.uc

@ -9,7 +9,7 @@
* Fix only works with vanilla pistols, as it's unpredictable what
* custom ones can do and they can handle these issues on their own
* in a better way.
* Copyright 2020 Anton Tarasenko
* Copyright 2020 - 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@ -52,7 +52,8 @@ class FixDualiesCost extends Feature
* These issues are fixed by directly assigning
* proper values to `SellValue`. To do that we need to detect when player
* buys/sells/drops/picks up weapons, which we accomplish by catching
* `CheckReplacement()` event for weapon instances. This approach has two issues.
* `CheckReplacement()` event for weapon instances. This approach has two
* issues.
* One is that, if vanilla's code sets an incorrect sell value, -
* it's doing it after weapon is spawned and, therefore,
* after `CheckReplacement()` call, so we have, instead, to remember to do
@ -125,7 +126,8 @@ var private const array<DualiesPair> dualiesClasses;
// Describe sell values that need to be applied at earliest later point.
struct WeaponValuePair
{
var KFWeapon weapon;
// Reference to `KFWeapon` instance
var NativeActorRef weapon;
var float value;
};
var private const array<WeaponValuePair> pendingValues;
@ -133,13 +135,15 @@ var private const array<WeaponValuePair> pendingValues;
// Describe sell values of all currently existing single pistols.
struct WeaponDataRecord
{
var KFWeapon reference;
// Reference to `KFWeapon` instance
var NativeActorRef reference;
var class<KFWeapon> class;
var float value;
// The whole point of this structure is to remember value of a weapon
// after it's destroyed. Since `reference` will become `none` by then,
// we will use the `owner` reference to identify the weapon.
var Pawn owner;
// Reference to `Pawn`.
var NativeActorRef owner;
};
var private const array<WeaponDataRecord> storedValues;
@ -148,45 +152,46 @@ var private int nextSellValue;
protected function OnEnabled()
{
local KFWeapon nextWeapon;
// Find all frags, that spawned when this fix wasn't running.
local LevelInfo level;
local KFWeapon nextWeapon;
_.unreal.OnTick(self).connect = Tick;
_.unreal.gameRules.OnOverridePickupQuery(self).connect = PickupQuery;
level = _.unreal.GetLevel();
// Find all weapons, that spawned when this fix wasn't running.
foreach level.DynamicActors(class'KFMod.KFWeapon', nextWeapon) {
RegisterSinglePistol(nextWeapon, false);
}
level.game.AddGameModifier(Spawn(class'DualiesCostRule'));
}
protected function OnDisabled()
{
local GameRules rulesIter;
local DualiesCostRule ruleToDestroy;
// Check first rule
if (level.game.gameRulesModifiers == none) return;
ruleToDestroy = DualiesCostRule(level.game.gameRulesModifiers);
if (ruleToDestroy != none)
local int i;
_.unreal.OnTick(self).Disconnect();
_.unreal.gameRules.OnOverridePickupQuery(self).Disconnect();
for (i = 0; i < storedValues.length; i += 1)
{
level.game.gameRulesModifiers = ruleToDestroy.nextGameRules;
ruleToDestroy.Destroy();
return;
storedValues[i].reference.FreeSelf();
storedValues[i].owner.FreeSelf();
}
// Check rest of the rules
rulesIter = level.game.gameRulesModifiers;
while (rulesIter != none)
{
ruleToDestroy = DualiesCostRule(rulesIter.nextGameRules);
if (ruleToDestroy != none)
{
rulesIter.nextGameRules = ruleToDestroy.nextGameRules;
ruleToDestroy.Destroy();
}
rulesIter = rulesIter.nextGameRules;
for (i = 0; i < pendingValues.length; i += 1) {
pendingValues[i].weapon.FreeSelf();
}
}
public final function SetNextSellValue(int newValue)
function bool PickupQuery(
Pawn other,
Pickup item,
out byte allowPickup)
{
nextSellValue = newValue;
local KFWeaponPickup weaponPickup;
weaponPickup = KFWeaponPickup(item);
if (weaponPickup != none)
{
ApplyPendingValues();
StoreSinglePistolValues();
nextSellValue = weaponPickup.sellValue;
}
return false;
}
// Finds a weapon of a given class in given `Pawn`'s inventory.
@ -270,15 +275,13 @@ public final function RegisterSinglePistol(
if (singlePistol == none) return;
if (GetIndexAs(singlePistol, true) < 0) return;
newRecord.reference = singlePistol;
newRecord.reference = _.unreal.ActorRef(singlePistol);
newRecord.class = singlePistol.class;
newRecord.owner = singlePistol.instigator;
if (justSpawned)
{
newRecord.owner = _.unreal.ActorRef(singlePistol.instigator);
if (justSpawned) {
newRecord.value = nextSellValue;
}
else
{
else {
newRecord.value = singlePistol.sellValue;
}
storedValues[storedValues.length] = newRecord;
@ -332,7 +335,7 @@ public final function FixCostAfterBuying(KFWeapon dualPistols)
// `dualPistols` will be the new pair of pistols, but it's value will
// get overwritten by vanilla's code after this function.
// So we must add it to pending values to be changed later.
newPendingValue.weapon = dualPistols;
newPendingValue.weapon = _.unreal.ActorRef(dualPistols);
if (dualPistols.class == class'KFMod.Dualies')
{
// 9mm is an exception.
@ -383,50 +386,59 @@ public final function FixCostAfterPickUp(KFWeapon dualPistols)
}
for (i = 0; i < storedValues.length; i += 1)
{
if (storedValues[i].reference != none) continue;
if (storedValues[i].reference.Get() != none) continue;
if (storedValues[i].class != dualiesClasses[index].single) continue;
if (storedValues[i].owner != dualPistols.instigator) continue;
newPendingValue.weapon = dualPistols;
if (storedValues[i].owner.Get() != dualPistols.instigator) continue;
newPendingValue.weapon = _.unreal.ActorRef(dualPistols);
newPendingValue.value = storedValues[i].value + nextSellValue;
pendingValues[pendingValues.length] = newPendingValue;
break;
}
}
public final function ApplyPendingValues()
private final function ApplyPendingValues()
{
local int i;
local int i;
local KFWeapon nextWeapon;
for (i = 0; i < pendingValues.length; i += 1)
{
if (pendingValues[i].weapon == none) continue;
nextWeapon = KFWeapon(pendingValues[i].weapon.Get());
pendingValues[i].weapon.FreeSelf();
if (nextWeapon == none) {
continue;
}
// Our fixes can only increase the correct (`!= -1`)
// sell value of weapons, so if we only need to change sell value
// if we're allowed to increase it or it's incorrect.
if (allowSellValueIncrease || pendingValues[i].weapon.sellValue == -1) {
pendingValues[i].weapon.sellValue = pendingValues[i].value;
if (allowSellValueIncrease || nextWeapon.sellValue == -1) {
nextWeapon.sellValue = pendingValues[i].value;
}
}
pendingValues.length = 0;
}
public final function StoreSinglePistolValues()
private final function StoreSinglePistolValues()
{
local int i;
i = 0;
local int i;
local KFWeapon nextWeapon;
while (i < storedValues.length)
{
if (storedValues[i].reference == none)
nextWeapon = KFWeapon(storedValues[i].reference.Get());
if (nextWeapon == none)
{
storedValues[i].reference.FreeSelf();
storedValues[i].owner.FreeSelf();
storedValues.Remove(i, 1);
continue;
}
storedValues[i].owner = storedValues[i].reference.instigator;
storedValues[i].value = storedValues[i].reference.sellValue;
storedValues[i].owner.Set(nextWeapon.instigator);
storedValues[i].value = nextWeapon.sellValue;
i += 1;
}
}
event Tick(float delta)
private function Tick(float delta, float timeDilationCoefficient)
{
ApplyPendingValues();
StoreSinglePistolValues();

75
sources/FixFFHack/FFHackRule.uc

@ -1,75 +0,0 @@
/**
* This rule detects suspicious attempts to deal damage and
* applies friendly fire scaling according to `FixFFHack`'s rules.
* Copyright 2019 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 FFHackRule extends GameRules;
function int NetDamage(
int originalDamage,
int damage,
Pawn injured,
Pawn instigator,
Vector hitLocation,
out Vector momentum,
class<DamageType> damageType
)
{
local KFGameType gameType;
local FixFFHack ffHackFix;
gameType = KFGameType(level.game);
// Something is very wrong and we can just bail on this damage
if (damageType == none || gameType == none) {
return 0;
}
// We only check when suspicious instigators that aren't a world
if (!damageType.default.bCausedByWorld && IsSuspicious(instigator))
{
ffHackFix = FixFFHack(class'FixFFHack'.static.GetInstance());
if (ffHackFix != none && ffHackFix.ShouldScaleDamage(damageType))
{
// Remove pushback to avoid environmental kills
momentum = Vect(0.0, 0.0, 0.0);
damage *= gameType.friendlyFireScale;
}
}
return super.NetDamage( originalDamage, damage, injured, instigator,
hitLocation, momentum, damageType);
}
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;
}
defaultproperties
{
}

46
sources/FixFFHack/FixFFHack.uc

@ -60,12 +60,54 @@ var private config const array< class<DamageType> > neverScale;
protected function OnEnabled()
{
_.unreal.AddGameRules(class'FFHackRule');
_.unreal.gameRules.OnNetDamage(self).connect = NetDamage;
}
protected function OnDisabled()
{
_.unreal.RemoveGameRules(class'FFHackRule');
_.unreal.gameRules.OnNetDamage(self).Disconnect();
}
function int NetDamage(
int originalDamage,
int damage,
Pawn injured,
Pawn instigator,
Vector hitLocation,
out Vector momentum,
class<DamageType> damageType)
{
// Something is very wrong and we can just bail on this damage
if (damageType == none) {
return 0;
}
// We only check when suspicious instigators that aren't a world
if (!damageType.default.bCausedByWorld && IsSuspicious(instigator))
{
if (ShouldScaleDamage(damageType))
{
// Remove pushback to avoid environmental kills
momentum = Vect(0.0, 0.0, 0.0);
damage *= _.unreal.GetKFGameType().friendlyFireScale;
}
}
return damage;
}
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;
}
// Checks general rule and exception list

59
sources/FixInfiniteNades/FixInfiniteNades.uc

@ -51,20 +51,28 @@ class FixInfiniteNades extends Feature
// so if you wish to prevent it, keep this flag set to `false`.
var private config const bool ignoreTossFlags;
// Set to `true` when this fix is getting disabled to avoid replacing the
// fire class again.
var private bool shuttingDown;
// Records how much ammo given frag grenade (`Frag`) has.
struct FragAmmoRecord
{
var public Frag fragReference;
var public int amount;
// Reference to `Frag`
var public NativeActorRef fragReference;
var public int amount;
};
var private array<FragAmmoRecord> ammoRecords;
protected function OnEnabled()
{
local Frag nextFrag;
local Frag nextFrag;
local LevelInfo level;
level = _.unreal.GetLevel();
_.unreal.OnTick(self).connect = Tick;
shuttingDown = false;
// Find all frags, that spawned when this fix wasn't running.
foreach level.DynamicActors(class'KFMod.Frag', nextFrag)
{
foreach level.DynamicActors(class'KFMod.Frag', nextFrag) {
RegisterFrag(nextFrag);
}
RecreateFrags();
@ -72,10 +80,19 @@ protected function OnEnabled()
protected function OnDisabled()
{
_.unreal.OnTick(self).Disconnect();
shuttingDown = true;
RecreateFrags();
ammoRecords.length = 0;
}
// Returns `true` when this feature is in the process of shutting down,
// which means nades' fire class should not be replaced.
public final function bool IsShuttingDown()
{
return shuttingDown;
}
// Returns index of the connection corresponding to the given controller.
// Returns `-1` if no connection correspond to the given controller.
// Returns `-1` if given controller is equal to `none`.
@ -86,8 +103,7 @@ private final function int GetAmmoIndex(Frag fragToCheck)
for (i = 0; i < ammoRecords.length; i += 1)
{
if (ammoRecords[i].fragReference == fragToCheck)
{
if (ammoRecords[i].fragReference.Get() == fragToCheck) {
return i;
}
}
@ -99,18 +115,20 @@ private final function RecreateFrags()
{
local int i;
local float maxAmmo, currentAmmo;
local Frag newFrag;
local Frag newFrag, oldFrag;
local Pawn fragOwner;
local array<FragAmmoRecord> oldRecords;
oldRecords = ammoRecords;
for (i = 0; i < oldRecords.length; i += 1)
{
// Check if we even need to recreate that instance of `Frag`
if (oldRecords[i].fragReference == none) continue;
fragOwner = oldRecords[i].fragReference.instigator;
if (fragOwner == none) continue;
oldFrag = Frag(oldRecords[i].fragReference.Get());
oldRecords[i].fragReference.FreeSelf();
if (oldFrag == none) continue;
fragOwner = oldFrag.instigator;
if (fragOwner == none) continue;
// Recreate
oldRecords[i].fragReference.Destroy();
oldFrag.Destroy();
fragOwner.CreateInventory("KFMod.Frag");
newFrag = GetPawnFrag(fragOwner);
// Restore ammo amount
@ -132,8 +150,7 @@ static private final function Frag GetPawnFrag(Pawn pawnWithFrag)
while (invIter != none)
{
foundFrag = Frag(invIter);
if (foundFrag != none)
{
if (foundFrag != none) {
return foundFrag;
}
invIter = invIter.inventory;
@ -160,7 +177,7 @@ public final function RegisterFrag(Frag newFrag)
index = GetAmmoIndex(newFrag);
if (index >= 0) return;
newRecord.fragReference = newFrag;
newRecord.fragReference = _.unreal.ActorRef(newFrag);
newRecord.amount = GetFragAmmo(newFrag);
ammoRecords[ammoRecords.length] = newRecord;
}
@ -207,20 +224,22 @@ private final function ReduceGrenades(Frag relevantFrag)
ammoRecords[index].amount -= 1;
}
event Tick(float delta)
private function Tick(float delta, float timeDilationCoefficient)
{
local int i;
local int i;
local Frag nextFrag;
// Update our ammo records with current, correct data.
i = 0;
while (i < ammoRecords.length)
{
if (ammoRecords[i].fragReference != none)
nextFrag = Frag(ammoRecords[i].fragReference.Get());
if (nextFrag != none)
{
ammoRecords[i].amount = GetFragAmmo(ammoRecords[i].fragReference);
ammoRecords[i].amount = GetFragAmmo(nextFrag);
i += 1;
}
else
{
ammoRecords[i].fragReference.FreeSelf();
ammoRecords.Remove(i, 1);
}
}

3
sources/FixInfiniteNades/FixedFragFire.uc

@ -25,8 +25,7 @@ function DoFireEffect()
{
local FixInfiniteNades nadeFix;
nadeFix = FixInfiniteNades(class'FixInfiniteNades'.static.GetInstance());
if (nadeFix == none || nadeFix.RegisterNadeThrow(Frag(weapon)))
{
if (nadeFix == none || nadeFix.RegisterNadeThrow(Frag(weapon))) {
super.DoFireEffect();
}
}

3
sources/FixInfiniteNades/MutatorListener_FixInfiniteNades.uc

@ -26,7 +26,8 @@ static function bool CheckReplacement(Actor other, out byte isSuperRelevant)
local Frag relevantFrag;
local FixInfiniteNades nadeFix;
nadeFix = FixInfiniteNades(class'FixInfiniteNades'.static.GetInstance());
if (nadeFix == none) return true;
if (nadeFix == none) return true;
if (nadeFix.IsShuttingDown()) return true;
// Handle detecting new frag (weapons that allows to throw nades)
relevantFrag = Frag(other);

14
sources/FixInventoryAbuse/FixInventoryAbuse.uc

@ -35,6 +35,8 @@ class FixInventoryAbuse extends Feature
// do this check too often.
var private config const float checkInterval;
var private Timer checkTimer;
struct DualiesPair
{
var class<KFWeaponPickup> single;
@ -52,12 +54,13 @@ protected function OnEnabled()
if (actualInterval <= 0) {
actualInterval = 0.25;
}
SetTimer(actualInterval, true);
checkTimer = _.time.StartTimer(actualInterval, true);
checkTimer.OnElapsed(self).connect = Timer;
}
protected function OnDisabled()
{
SetTimer(0.0f, false);
_.memory.Free(checkTimer);
}
// Did player with this controller contribute to the latest dosh generation?
@ -84,7 +87,7 @@ private final function class<KFWeaponPickup> GetRootPickupClass(KFWeapon weapon)
if (weapon == none) return none;
// Start with a pickup of the given weapons
root = class<KFWeaponPickup>(weapon.default.pickupClass);
if (root == none) return none;
if (root == none) return none;
// In case it's a dual version - find corresponding single pickup class
// (it's root would be the same).
@ -181,13 +184,12 @@ private final function DropEverything(KFHumanPawn playerPawn)
weaponList[weaponList.length] = nextWeapon;
}
// And destroy them later.
for(i = 0; i < weaponList.length; i += 1)
{
for(i = 0; i < weaponList.length; i += 1) {
DropWeapon(weaponList[i]);
}
}
event Timer()
private function Timer(Timer source)
{
local int i;
local KFHumanPawn nextPawn;

67
sources/FixLogSpam/SpamPickup/HelperPickup.uc

@ -72,12 +72,12 @@ class HelperPickup extends AcediaObject
*/
// For easy access in relevant `GameRules` and mutator event listener.
var private HelperPickup singletonInstance;
var private HelperPickup singletonInstance;
// Already fixed pickups that we periodically "refix".
var private array<KFWeaponPickup> recordedPickups;
var private array<NativeActorRef> recordedPickups;
// Pickups that will get `bDropped` flag checked the next tick to
// determine if we need to fix them.
var private array<KFWeaponPickup> pendingPickups;
var private array<NativeActorRef> pendingPickups;
protected function Constructor()
{
@ -88,7 +88,7 @@ protected function Constructor()
}
// To detect when player tries to pick something up
// (and force additional pickup fix update)
_.unreal.AddGameRules(class'PickupSpamRule');
_.unreal.gameRules.OnOverridePickupQuery(self).connect = PickupQuery;
// To detect newly spawned pickups
class'MutatorListener_FixLogSpam_Pickup'.static.SetActive(true);
// Find all `KFWeaponPickup`s laying around on the map,
@ -99,22 +99,26 @@ protected function Constructor()
// kind of pickup.
level = _.unreal.GetLevel();
foreach level.DynamicActors(class'KFMod.KFWeaponPickup', nextPickup) {
pendingPickups[pendingPickups.length] = nextPickup;
pendingPickups[pendingPickups.length] = _.unreal.ActorRef(nextPickup);
}
}
protected function Finalizer()
{
local int i;
local int i;
local KFWeaponPickup nextPickup;
for (i = 0; i < recordedPickups.length; i += 1)
{
if (recordedPickups[i] != none && !recordedPickups[i].bPendingDelete) {
nextPickup = KFWeaponPickup(recordedPickups[i].Get());
if (nextPickup != none) {
recordedPickups[i].Enable('Destroyed');
}
}
_.memory.FreeMany(recordedPickups);
_.memory.FreeMany(pendingPickups);
recordedPickups.length = 0;
pendingPickups.length = 0;
_.unreal.RemoveGameRules(class'PickupSpamRule');
_.unreal.gameRules.OnOverridePickupQuery(self).Disconnect();
class'MutatorListener_FixLogSpam_Pickup'.static.SetActive(false);
}
@ -128,19 +132,22 @@ public final static function HelperPickup GetInstance()
// `none` is never recorded.
private final function bool IsPickupRecorded(KFWeaponPickup pickupToCheck)
{
local int i;
local int i;
local KFWeaponPickup nextPickup;
if (pickupToCheck == none) {
return false;
}
for (i = 0; i < recordedPickups.length; i += 1)
{
if (recordedPickups[i] == pickupToCheck) {
nextPickup = KFWeaponPickup(recordedPickups[i].Get());
if (nextPickup == pickupToCheck) {
return true;
}
}
for (i = 0; i < pendingPickups.length; i += 1)
{
if (pendingPickups[i] == pickupToCheck) {
nextPickup = KFWeaponPickup(pendingPickups[i].Get());
if (nextPickup == pickupToCheck) {
return true;
}
}
@ -154,7 +161,9 @@ private final function CleanRecordedPickups()
local int i;
while (i < recordedPickups.length)
{
if (recordedPickups[i] == none) {
if (recordedPickups[i].Get() == none)
{
recordedPickups[i].FreeSelf();
recordedPickups.Remove(i, 1);
}
else {
@ -173,35 +182,47 @@ public final function HandlePickup(KFWeaponPickup newPickup)
if (newPickup.instigator != none)
{
newPickup.Disable('Destroyed');
recordedPickups[recordedPickups.length] = newPickup;
recordedPickups[recordedPickups.length] = _.unreal.ActorRef(newPickup);
}
else {
pendingPickups[pendingPickups.length] = newPickup;
pendingPickups[pendingPickups.length] = _.unreal.ActorRef(newPickup);
}
}
// Re-disables `Destroyed()` event for all recorded (confirmed) dropped pickups
// and processes all pending (for the check if they are dropped) pickups.
public final function UpdatePickups()
private final function UpdatePickups()
{
local int i;
local int i;
local KFWeaponPickup nextPickup;
for (i = 0; i < recordedPickups.length; i += 1)
{
if (recordedPickups[i] != none) {
recordedPickups[i].Disable('Destroyed');
nextPickup = KFWeaponPickup(recordedPickups[i].Get());
if (nextPickup != none) {
nextPickup.Disable('Destroyed');
}
}
for (i = 0; i < pendingPickups.length; i += 1)
{
if (pendingPickups[i].bDropped)
{
pendingPickups[i].Disable('Destroyed');
recordedPickups[recordedPickups.length] = pendingPickups[i];
}
nextPickup = KFWeaponPickup(pendingPickups[i].Get());
if (nextPickup == none) continue;
if (nextPickup.bDropped) continue;
nextPickup.Disable('Destroyed');
recordedPickups[recordedPickups.length] = pendingPickups[i];
}
pendingPickups.length = 0;
}
function bool PickupQuery(
Pawn toucher,
Pickup touchedPickup,
out byte allowPickup)
{
UpdatePickups();
return false;
}
public final function Tick()
{
CleanRecordedPickups();

38
sources/FixLogSpam/SpamPickup/PickupSpamRule.uc

@ -1,38 +0,0 @@
/**
* This rule detects when someone is trying to... pick up a pickup and
* forces additional fixing update on all the ones, dropped by players.
* 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 PickupSpamRule extends GameRules;
function bool OverridePickupQuery(
Pawn toucher,
Pickup touchedPickup,
out byte allowPickup)
{
local HelperPickup helper;
helper = class'HelperPickup'.static.GetInstance();
if (helper != none) {
helper.UpdatePickups();
}
return super.OverridePickupQuery(toucher, touchedPickup, allowPickup);
}
defaultproperties
{
}

48
sources/FixLogSpam/SpamTrader/HelperTrader.uc

@ -50,9 +50,11 @@ class HelperTrader extends AcediaObject
// Remember instance of `WeaponLocker` that we spawned, so we can clean
// it up later.
var private WeaponLocker dummyLocker;
// Reference to `WeaponLocker`
var private NativeActorRef dummyLocker;
// Remember fixed shops, so we can unfix them later.
var private array<ShopVolume> fixedShops;
// Reference tp `ShopVolume`.
var private array<NativeActorRef> fixedShops;
var LoggerAPI.Definition errNoReplacementLocker;
@ -70,6 +72,7 @@ protected function Constructor()
if (replacementLocker == none)
{
_.logger.Auto(errNoReplacementLocker);
FreeSelf();
return;
}
// Setup locker
@ -78,29 +81,41 @@ protected function Constructor()
if (nextShop == none) continue;
if (nextShop.myTrader != none) continue;
nextShop.myTrader = replacementLocker;
fixedShops[fixedShops.length] = nextShop;
fixedShops[fixedShops.length] = _.unreal.ActorRef(nextShop);
}
}
protected function Finalizer()
{
local int i;
local int i;
local ShopVolume nextShop;
local Actor lockerInstance;
// Free shops
for (i = 0; i < fixedShops.length; i += 1)
{
if (fixedShops[i] != none) {
fixedShops[i].myTrader = none;
nextShop = ShopVolume(fixedShops[i].Get());
fixedShops[i].FreeSelf();
if (nextShop != none) {
nextShop.myTrader = none;
}
}
if (dummyLocker != none && !dummyLocker.bPendingDelete) {
dummyLocker.Destroy();
fixedShops.length = 0;
// Free locker
if (dummyLocker != none) {
lockerInstance = dummyLocker.Get();
dummyLocker.FreeSelf();
}
if (lockerInstance != none) {
lockerInstance.Destroy();
}
}
private final function WeaponLocker SpawnDummyWeaponLocker()
{
local WeaponLocker lockerInstance;
local bool savedCollideWorld, savedCollideActors;
if (dummyLocker != none && !dummyLocker.bPendingDelete) {
return dummyLocker;
if (dummyLocker != none) {
return WeaponLocker(dummyLocker.Get());
}
// Disable collision so that dummy `WeaponLocker` would both not affect
// gameplay and would not fail spawning due to being inside terrain or
@ -109,16 +124,17 @@ private final function WeaponLocker SpawnDummyWeaponLocker()
savedCollideActors = class'WeaponLocker'.default.bBlockActors;
class'WeaponLocker'.default.bCollideWorld = false;
class'WeaponLocker'.default.bBlockActors = false;
dummyLocker = WeaponLocker(_.memory.Allocate(class'WeaponLocker'));
lockerInstance = WeaponLocker(_.memory.Allocate(class'WeaponLocker'));
class'WeaponLocker'.default.bCollideWorld = savedCollideWorld;
class'WeaponLocker'.default.bBlockActors = savedCollideActors;
if (dummyLocker != none)
if (lockerInstance != none)
{
dummyLocker.bHidden = true;
dummyLocker.SetDrawType(DT_None);
dummyLocker.SetCollision(false);
lockerInstance.bHidden = true;
lockerInstance.SetDrawType(DT_None);
lockerInstance.SetCollision(false);
}
return dummyLocker;
dummyLocker = _.unreal.ActorRef(lockerInstance);
return lockerInstance;
}
private final function WeaponLocker FindExistingWeaponLocker()

110
sources/FixPipes/FixPipes.uc

@ -100,18 +100,20 @@ var public const config float proximityCheckElevation;
// else we need to track.
struct PipeRecord
{
// Pipe that this record tracks
var PipeBombProjectile pipe;
// Pipe that this record tracks.
// Reference to `PipeBombProjectile`.
var NativeActorRef 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;
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
var bool proximityCheckIntercepted;
// Reference to the `ExtendedZCollision` we created to catch
// `TakeDamage()` event.
var PipesSafetyCollision safetyCollision;
// Reference to `PipesSafetyCollision`.
var NativeActorRef safetyCollision;
};
var private array<PipeRecord> pipeRecords;
@ -121,12 +123,20 @@ var private array<PipeRecord> pipeRecords;
// pipe bombs spawning).
var private bool pipesRelevancyFlag;
var private Timer cleanupTimer;
protected function OnEnabled()
{
local PipeBombProjectile nextPipe;
local LevelInfo level;
local PipeBombProjectile nextPipe;
pipesRelevancyFlag = class'PipeBombProjectile'.default.bAlwaysRelevant;
class'PipeBombProjectile'.default.bGameRelevant = false;
// Set cleanup timer, there is little point to making
// clean up interval configurable.
cleanupTimer = _.time.StartTimer(5.0, true);
cleanupTimer.OnElapsed(self).connect = CleanPipeRecords;
// Fix pipes that are already lying about on the map
level = _.unreal.GetLevel();
foreach level.DynamicActors(class'KFMod.PipeBombProjectile', nextPipe) {
RegisterPipe(nextPipe);
}
@ -136,6 +146,7 @@ protected function OnDisabled()
{
local int i;
class'PipeBombProjectile'.default.bGameRelevant = pipesRelevancyFlag;
cleanupTimer.FreeSelf();
for (i = 0; i < pipeRecords.length; i += 1) {
ReleasePipe(pipeRecords[i]);
}
@ -154,19 +165,18 @@ public final function RegisterPipe(PipeBombProjectile newPipe)
// Check whether we have already added this pipe
for (i = 0; i < pipeRecords.length; i += 1)
{
if (pipeRecords[i].pipe == newPipe) {
if (pipeRecords[i].pipe.Get() == newPipe) {
return;
}
}
newRecord.pipe = newPipe;
newRecord.pipe = _.unreal.ActorRef(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.safetyCollision = _.unreal.ActorRef(
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())
@ -181,16 +191,26 @@ public final function RegisterPipe(PipeBombProjectile newPipe)
// Rolls back our changes to the pipe in the given `PipeRecord`.
public final function ReleasePipe(PipeRecord pipeRecord)
{
local PipeBombProjectile pipe;
local PipesSafetyCollision safetyCollision;
if (pipeRecord.safetyCollision != none)
{
pipeRecord.safetyCollision.TurnOff();
safetyCollision =
PipesSafetyCollision(pipeRecord.safetyCollision.Get());
pipeRecord.safetyCollision.FreeSelf();
pipeRecord.safetyCollision = none;
}
if (pipeRecord.proximityCheckIntercepted && pipeRecord.pipe != none)
if (safetyCollision != none) {
safetyCollision.TurnOff();
}
pipe = PipeBombProjectile(pipeRecord.pipe.Get());
pipeRecord.pipe.FreeSelf();
pipeRecord.pipe = none;
if (pipeRecord.proximityCheckIntercepted && pipe != none)
{
pipeRecord.proximityCheckIntercepted = false;
if (IsPipeDoingProximityChecks(pipeRecord.pipe)) {
pipeRecord.pipe.SetTimer(pipeRecord.timerCountDown, true);
if (IsPipeDoingProximityChecks(pipe)) {
pipe.SetTimer(pipeRecord.timerCountDown, true);
}
}
}
@ -215,12 +235,15 @@ private final function bool NeedManagedProximityChecks()
}
// Removes dead records with pipe instances turned into `none`
private final function CleanPipeRecords()
private final function CleanPipeRecords(Timer source)
{
local int i;
while (i < pipeRecords.length)
{
if (pipeRecords[i].pipe == none) {
if (pipeRecords[i].pipe.Get() == none)
{
_.memory.Free(pipeRecords[i].pipe);
_.memory.Free(pipeRecords[i].safetyCollision);
pipeRecords.Remove(i, 1);
}
else {
@ -235,14 +258,18 @@ private final function CleanPipeRecords()
// 2. Have not yet had it's logic replaced.
private final function InterceptProximityChecks()
{
local int i;
local int i;
local PipeBombProjectile nextPipe;
for (i = 0; i < pipeRecords.length; i += 1)
{
if (pipeRecords[i].proximityCheckIntercepted) continue;
if (pipeRecords[i].pipe == none) continue;
if (IsPipeDoingProximityChecks(pipeRecords[i].pipe))
nextPipe = PipeBombProjectile(pipeRecords[i].pipe.Get());
if (nextPipe == none) continue;
if (IsPipeDoingProximityChecks(nextPipe))
{
pipeRecords[i].pipe.SetTimer(0, false);
// Turn off pipe's own timer
nextPipe.SetTimer(0, false);
// We set `1.0` because that is the vanilla value;
// Line 123 of "PipeBombProjectile.uc": `SetTimer(1.0,True);`
pipeRecords[i].timerCountDown = 1.0;
@ -267,30 +294,29 @@ private final function bool IsPipeDoingProximityChecks(PipeBombProjectile pipe)
// for them
private final function PerformProximityChecks(float delta)
{
local int i;
local Vector checkLocation;
local int i;
local Vector checkLocation;
local PipeBombProjectile nextPipe;
for (i = 0; i < pipeRecords.length; i += 1)
{
if (pipeRecords[i].pipe == none) continue;
pipeRecords[i].timerCountDown -= delta;
if (pipeRecords[i].timerCountDown > 0) continue;
nextPipe = PipeBombProjectile(pipeRecords[i].pipe.Get());
if (nextPipe == none) continue;
// `timerCountDown` does not makes sense for pipes that
// are not doing proxiity checks
if (!IsPipeDoingProximityChecks(pipeRecords[i].pipe)) continue;
if (!IsPipeDoingProximityChecks(nextPipe)) 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);
checkLocation = nextPipe.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
@ -319,12 +345,15 @@ private final function DoPipeProximityCheck(
local Pawn checkPawn;
local float threatLevel;
local PipeBombProjectile pipe;
pipe = pipeRecord.pipe;
pipe = PipeBombProjectile(pipeRecord.pipe.Get());
if (pipe == none) {
return;
}
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)
foreach pipe.VisibleCollidingActors( class'Pawn', checkPawn,
pipe.detectionRadius, checkLocation)
{
threatLevel += GetThreatLevel(pipe, checkPawn);
// Explosion! No need to bother with the rest of the `Pawn`s.
@ -402,7 +431,6 @@ private final function float GetThreatLevel(
event Tick(float delta)
{
CleanPipeRecords();
if (NeedManagedProximityChecks())
{
InterceptProximityChecks();

8
sources/FixProjectileFF/FixProjectileFF.uc

@ -134,16 +134,12 @@ public final static function class<ROBallisticProjectile> FindFixedClass(
// If `FixProjectileFF` in disabled always returns `false`.
public final static function bool IsFriendlyFireAcceptable()
{
local TeamGame gameType;
local FixProjectileFF projectileFFFix;
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;
return __().unreal.GetKFGameType().friendlyFireScale > 0;
}
defaultproperties

3
sources/FixProjectileFF/MutatorListener_FixProjectileFF.uc

@ -155,7 +155,8 @@ static function MoveImpactDamage(
newProjectileAsHarpoon = SealSquealProjectile(newProjectile);
if (oldProjectileAsHarpoon != none && newProjectileAsHarpoon != none)
{
newProjectileAsHarpoon.impactDamage = oldProjectileAsHarpoon.impactDamage;
newProjectileAsHarpoon.impactDamage =
oldProjectileAsHarpoon.impactDamage;
oldProjectileAsHarpoon.impactDamage = 0;
return;
}

46
sources/FixSpectatorCrash/FixSpectatorCrash.uc

@ -70,8 +70,9 @@ var private config const bool allowServerBlock;
// spectator change per player.
struct CooldownRecord
{
var PlayerController player;
var float cooldown;
// Reference to `PlayerController`
var NativeActorRef player;
var float cooldown;
};
// Currently active cooldowns
@ -81,7 +82,8 @@ var private array<CooldownRecord> currentCooldowns;
// were marked for disconnecting.
// We'll be maintaining server block as long as even one
// of them hasn't yet disconnected.
var private array<PlayerController> violators;
// References to `PlayerController`s.
var private array<NativeActorRef> violators;
// Is server currently blocked?
var private bool becomingActiveBlocked;
@ -95,6 +97,16 @@ var private bool becomingActiveBlocked;
// compatibility on it's own.
var private int recordedNumPlayersMod;
protected function OnEnabled()
{
_.unreal.OnTick(self).connect = Tick;
}
protected function OnDisabled()
{
_.unreal.OnTick(self).Disconnect();
}
// If given `PlayerController` is registered in our cooldown records, -
// returns it's index.
// If it doesn't exists (or `none` value was passes), - returns `-1`.
@ -105,7 +117,7 @@ private final function int GetCooldownIndex(PlayerController player)
for (i = 0; i < currentCooldowns.length; i += 1)
{
if (currentCooldowns[i].player == player) {
if (currentCooldowns[i].player.Get() == player) {
return i;
}
}
@ -121,7 +133,7 @@ public final function bool IsViolator(PlayerController player)
for (i = 0; i < violators.length; i += 1)
{
if (violators[i] == player) {
if (violators[i].Get() == player) {
return true;
}
}
@ -142,8 +154,8 @@ public final function NotifyStatusChange(PlayerController player)
if (index >= 0)
{
player.Destroy();
violators[violators.length] = currentCooldowns[index].player;
currentCooldowns.Remove(index, 1);
violators[violators.length] = player;
if (allowServerBlock) {
SetBlock(true);
}
@ -153,7 +165,7 @@ public final function NotifyStatusChange(PlayerController player)
// or didn't recently change their status (put them on cooldown).
else if (!IsViolator(player))
{
newRecord.player = player;
newRecord.player = _.unreal.ActorRef(player);
newRecord.cooldown = spectatorChangeTimeout;
currentCooldowns[currentCooldowns.length] = newRecord;
}
@ -167,11 +179,9 @@ private final function SetBlock(bool activateBlock)
// Do we even need to do anything?
if (!allowServerBlock) return;
if (activateBlock == becomingActiveBlocked) return;
// Only works with `KFGameType` and it's children.
if (level != none) kfGameType = KFGameType(level.game);
if (kfGameType == none) return;
// Actually block/unblock
kfGameType = _.unreal.GetKFGameType();
becomingActiveBlocked = activateBlock;
if (activateBlock)
{
@ -198,10 +208,12 @@ private final function TryUnblocking()
for (i = 0; i < violators.length; i += 1)
{
if (violators[i] != none) {
if (violators[i].Get() != none) {
return;
}
}
_.memory.FreeMany(violators);
violators.length = 0;
SetBlock(false);
}
@ -242,10 +254,7 @@ private final function int GetRealPlayers()
// (difference will be equal to the amount of faked players).
private final function int GetNumPlayersMod()
{
local KFGameType kfGameType;
if (level != none) kfGameType = KFGameType(level.game);
if (kfGameType == none) return 0;
return kfGameType.numPlayers - GetRealPlayers();
return _.unreal.GetKFGameType().numPlayers - GetRealPlayers();
}
private final function ReduceCooldowns(float timePassed)
@ -255,22 +264,23 @@ private final function ReduceCooldowns(float timePassed)
while (i < currentCooldowns.length)
{
currentCooldowns[i].cooldown -= timePassed;
if ( currentCooldowns[i].player != none
if ( currentCooldowns[i].player.Get() != none
&& currentCooldowns[i].cooldown > 0.0)
{
i += 1;
}
else
{
currentCooldowns[i].player.FreeSelf();
currentCooldowns.Remove(i, 1);
}
}
}
event Tick(float delta)
private function Tick(float delta, float tileDilationCoefficient)
{
local float trueTimePassed;
trueTimePassed = delta * (1.1 / level.timeDilation);
trueTimePassed = delta / tileDilationCoefficient;
TryUnblocking();
ReduceCooldowns(trueTimePassed);
}

47
sources/FixZedTimeLags/FixZedTimeLags.uc

@ -63,32 +63,28 @@ var private config const int maxGameSpeedUpdatesAmount;
var private config const bool disableTick;
// Counts how much time is left until next update
var private float updateCooldown;
// Recorded game type, to avoid constant conversions every tick
var private KFGameType gameType;
protected function OnEnabled()
{
gameType = KFGameType(level.game);
if (gameType == none) {
Destroy();
}
else if (disableTick) {
gameType.Disable('Tick');
if (disableTick) {
_.unreal.GetGameType().Disable('Tick');
}
_.unreal.OnTick(self).connect = Tick;
}
protected function OnDisabled()
{
gameType = KFGameType(level.game);
if (gameType != none && disableTick) {
gameType.Enable('Tick');
if (disableTick) {
_.unreal.GetGameType().Enable('Tick');
}
_.unreal.OnTick(self).Disconnect();
}
event Tick(float delta)
private function Tick(float delta, float timeDilationCoefficient)
{
local float trueTimePassed;
if (gameType == none) return;
local KFGameType gameType;
local float trueTimePassed;
gameType = _.unreal.GetKFGameType();
if (!gameType.bZEDTimeActive) return;
// Unfortunately we need to keep disabling `Tick()` probe function,
// because it constantly gets enabled back and I don't know where
@ -97,19 +93,19 @@ event Tick(float delta)
gameType.Disable('Tick');
}
// How much real (not in-game) time has passed
trueTimePassed = delta * (1.1 / level.timeDilation);
trueTimePassed = delta / timeDilationCoefficient;
gameType.currentZEDTimeDuration -= trueTimePassed;
// Handle speeding up phase
if (gameType.bSpeedingBackUp) {
DoSpeedBackUp(trueTimePassed);
DoSpeedBackUp(trueTimePassed, gameType);
}
else if (gameType.currentZEDTimeDuration < GetSpeedupDuration())
else if (gameType.currentZEDTimeDuration < GetSpeedupDuration(gameType))
{
gameType.bSpeedingBackUp = true;
updateCooldown = GetFullUpdateCooldown();
updateCooldown = GetFullUpdateCooldown(gameType);
TellClientsZedTimeEnds();
DoSpeedBackUp(trueTimePassed);
DoSpeedBackUp(trueTimePassed, gameType);
}
// End zed time once it's duration has passed
if (gameType.currentZEDTimeDuration <= 0)
@ -143,7 +139,7 @@ private final function TellClientsZedTimeEnds()
// This function is called every tick during speed up phase and manages
// gradual game speed increase.
private final function DoSpeedBackUp(float trueTimePassed)
private final function DoSpeedBackUp(float trueTimePassed, KFGameType gameType)
{
// Game speed will always be updated in our `Tick()` event
// at the very end of the zed time.
@ -159,21 +155,22 @@ private final function DoSpeedBackUp(float trueTimePassed)
return;
}
else {
updateCooldown = GetFullUpdateCooldown();
updateCooldown = GetFullUpdateCooldown(gameType);
}
slowdownScale = gameType.currentZEDTimeDuration / GetSpeedupDuration();
slowdownScale =
gameType.currentZEDTimeDuration / GetSpeedupDuration(gameType);
newGameSpeed = Lerp(slowdownScale, 1.0, gameType.zedTimeSlomoScale);
gameType.SetGameSpeed(newGameSpeed);
}
private final function float GetSpeedupDuration()
private final function float GetSpeedupDuration(KFGameType gameType)
{
return gameType.zedTimeDuration * 0.166;
}
private final function float GetFullUpdateCooldown()
private final function float GetFullUpdateCooldown(KFGameType gameType)
{
return GetSpeedupDuration() / maxGameSpeedUpdatesAmount;
return GetSpeedupDuration(gameType) / maxGameSpeedUpdatesAmount;
}
defaultproperties

Loading…
Cancel
Save