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. 51
      sources/FixAmmoSelling/FixAmmoSelling.uc
  3. 72
      sources/FixAmmoSelling/FixAmmoSellingService.uc
  4. 76
      sources/FixDoshSpam/FixDoshSpam.uc
  5. 44
      sources/FixDualiesCost/DualiesCostRule.uc
  6. 112
      sources/FixDualiesCost/FixDualiesCost.uc
  7. 75
      sources/FixFFHack/FFHackRule.uc
  8. 46
      sources/FixFFHack/FixFFHack.uc
  9. 51
      sources/FixInfiniteNades/FixInfiniteNades.uc
  10. 3
      sources/FixInfiniteNades/FixedFragFire.uc
  11. 1
      sources/FixInfiniteNades/MutatorListener_FixInfiniteNades.uc
  12. 12
      sources/FixInventoryAbuse/FixInventoryAbuse.uc
  13. 57
      sources/FixLogSpam/SpamPickup/HelperPickup.uc
  14. 38
      sources/FixLogSpam/SpamPickup/PickupSpamRule.uc
  15. 46
      sources/FixLogSpam/SpamTrader/HelperTrader.uc
  16. 84
      sources/FixPipes/FixPipes.uc
  17. 6
      sources/FixProjectileFF/FixProjectileFF.uc
  18. 3
      sources/FixProjectileFF/MutatorListener_FixProjectileFF.uc
  19. 44
      sources/FixSpectatorCrash/FixSpectatorCrash.uc
  20. 45
      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 ; We use `doshPerSecondLimitMax` when there's no dosh on the map and
; scale linearly between them as it's amount grows. ; scale linearly between them as it's amount grows.
criticalDoshAmount=25 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] [AcediaFixes.FixSpectatorCrash]

51
sources/FixAmmoSelling/FixAmmoSelling.uc

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

76
sources/FixDoshSpam/FixDoshSpam.uc

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

112
sources/FixDualiesCost/FixDualiesCost.uc

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

51
sources/FixInfiniteNades/FixInfiniteNades.uc

@ -51,10 +51,15 @@ class FixInfiniteNades extends Feature
// so if you wish to prevent it, keep this flag set to `false`. // so if you wish to prevent it, keep this flag set to `false`.
var private config const bool ignoreTossFlags; 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. // Records how much ammo given frag grenade (`Frag`) has.
struct FragAmmoRecord struct FragAmmoRecord
{ {
var public Frag fragReference; // Reference to `Frag`
var public NativeActorRef fragReference;
var public int amount; var public int amount;
}; };
var private array<FragAmmoRecord> ammoRecords; var private array<FragAmmoRecord> ammoRecords;
@ -62,9 +67,12 @@ var private array<FragAmmoRecord> ammoRecords;
protected function OnEnabled() 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. // 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); RegisterFrag(nextFrag);
} }
RecreateFrags(); RecreateFrags();
@ -72,10 +80,19 @@ protected function OnEnabled()
protected function OnDisabled() protected function OnDisabled()
{ {
_.unreal.OnTick(self).Disconnect();
shuttingDown = true;
RecreateFrags(); RecreateFrags();
ammoRecords.length = 0; 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 index of the connection corresponding to the given controller.
// Returns `-1` if no connection correspond to the given controller. // Returns `-1` if no connection correspond to the given controller.
// Returns `-1` if given controller is equal to `none`. // 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) for (i = 0; i < ammoRecords.length; i += 1)
{ {
if (ammoRecords[i].fragReference == fragToCheck) if (ammoRecords[i].fragReference.Get() == fragToCheck) {
{
return i; return i;
} }
} }
@ -99,18 +115,20 @@ private final function RecreateFrags()
{ {
local int i; local int i;
local float maxAmmo, currentAmmo; local float maxAmmo, currentAmmo;
local Frag newFrag; local Frag newFrag, oldFrag;
local Pawn fragOwner; local Pawn fragOwner;
local array<FragAmmoRecord> oldRecords; local array<FragAmmoRecord> oldRecords;
oldRecords = ammoRecords; oldRecords = ammoRecords;
for (i = 0; i < oldRecords.length; i += 1) for (i = 0; i < oldRecords.length; i += 1)
{ {
// Check if we even need to recreate that instance of `Frag` // Check if we even need to recreate that instance of `Frag`
if (oldRecords[i].fragReference == none) continue; oldFrag = Frag(oldRecords[i].fragReference.Get());
fragOwner = oldRecords[i].fragReference.instigator; oldRecords[i].fragReference.FreeSelf();
if (oldFrag == none) continue;
fragOwner = oldFrag.instigator;
if (fragOwner == none) continue; if (fragOwner == none) continue;
// Recreate // Recreate
oldRecords[i].fragReference.Destroy(); oldFrag.Destroy();
fragOwner.CreateInventory("KFMod.Frag"); fragOwner.CreateInventory("KFMod.Frag");
newFrag = GetPawnFrag(fragOwner); newFrag = GetPawnFrag(fragOwner);
// Restore ammo amount // Restore ammo amount
@ -132,8 +150,7 @@ static private final function Frag GetPawnFrag(Pawn pawnWithFrag)
while (invIter != none) while (invIter != none)
{ {
foundFrag = Frag(invIter); foundFrag = Frag(invIter);
if (foundFrag != none) if (foundFrag != none) {
{
return foundFrag; return foundFrag;
} }
invIter = invIter.inventory; invIter = invIter.inventory;
@ -160,7 +177,7 @@ public final function RegisterFrag(Frag newFrag)
index = GetAmmoIndex(newFrag); index = GetAmmoIndex(newFrag);
if (index >= 0) return; if (index >= 0) return;
newRecord.fragReference = newFrag; newRecord.fragReference = _.unreal.ActorRef(newFrag);
newRecord.amount = GetFragAmmo(newFrag); newRecord.amount = GetFragAmmo(newFrag);
ammoRecords[ammoRecords.length] = newRecord; ammoRecords[ammoRecords.length] = newRecord;
} }
@ -207,20 +224,22 @@ private final function ReduceGrenades(Frag relevantFrag)
ammoRecords[index].amount -= 1; 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. // Update our ammo records with current, correct data.
i = 0;
while (i < ammoRecords.length) 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; i += 1;
} }
else else
{ {
ammoRecords[i].fragReference.FreeSelf();
ammoRecords.Remove(i, 1); ammoRecords.Remove(i, 1);
} }
} }

3
sources/FixInfiniteNades/FixedFragFire.uc

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

1
sources/FixInfiniteNades/MutatorListener_FixInfiniteNades.uc

@ -27,6 +27,7 @@ static function bool CheckReplacement(Actor other, out byte isSuperRelevant)
local FixInfiniteNades nadeFix; local FixInfiniteNades nadeFix;
nadeFix = FixInfiniteNades(class'FixInfiniteNades'.static.GetInstance()); 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) // Handle detecting new frag (weapons that allows to throw nades)
relevantFrag = Frag(other); relevantFrag = Frag(other);

12
sources/FixInventoryAbuse/FixInventoryAbuse.uc

@ -35,6 +35,8 @@ class FixInventoryAbuse extends Feature
// do this check too often. // do this check too often.
var private config const float checkInterval; var private config const float checkInterval;
var private Timer checkTimer;
struct DualiesPair struct DualiesPair
{ {
var class<KFWeaponPickup> single; var class<KFWeaponPickup> single;
@ -52,12 +54,13 @@ protected function OnEnabled()
if (actualInterval <= 0) { if (actualInterval <= 0) {
actualInterval = 0.25; actualInterval = 0.25;
} }
SetTimer(actualInterval, true); checkTimer = _.time.StartTimer(actualInterval, true);
checkTimer.OnElapsed(self).connect = Timer;
} }
protected function OnDisabled() protected function OnDisabled()
{ {
SetTimer(0.0f, false); _.memory.Free(checkTimer);
} }
// Did player with this controller contribute to the latest dosh generation? // Did player with this controller contribute to the latest dosh generation?
@ -181,13 +184,12 @@ private final function DropEverything(KFHumanPawn playerPawn)
weaponList[weaponList.length] = nextWeapon; weaponList[weaponList.length] = nextWeapon;
} }
// And destroy them later. // And destroy them later.
for(i = 0; i < weaponList.length; i += 1) for(i = 0; i < weaponList.length; i += 1) {
{
DropWeapon(weaponList[i]); DropWeapon(weaponList[i]);
} }
} }
event Timer() private function Timer(Timer source)
{ {
local int i; local int i;
local KFHumanPawn nextPawn; local KFHumanPawn nextPawn;

57
sources/FixLogSpam/SpamPickup/HelperPickup.uc

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

46
sources/FixLogSpam/SpamTrader/HelperTrader.uc

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

84
sources/FixPipes/FixPipes.uc

@ -100,8 +100,9 @@ var public const config float proximityCheckElevation;
// else we need to track. // else we need to track.
struct PipeRecord struct PipeRecord
{ {
// Pipe that this record tracks // Pipe that this record tracks.
var PipeBombProjectile pipe; // Reference to `PipeBombProjectile`.
var NativeActorRef pipe;
// Each pipe has a separate timer for their scheduled proximity checks, // Each pipe has a separate timer for their scheduled proximity checks,
// so we need a separate variable to track when that time comes for // so we need a separate variable to track when that time comes for
// each and every pipe // each and every pipe
@ -111,7 +112,8 @@ struct PipeRecord
var bool proximityCheckIntercepted; var bool proximityCheckIntercepted;
// Reference to the `ExtendedZCollision` we created to catch // Reference to the `ExtendedZCollision` we created to catch
// `TakeDamage()` event. // `TakeDamage()` event.
var PipesSafetyCollision safetyCollision; // Reference to `PipesSafetyCollision`.
var NativeActorRef safetyCollision;
}; };
var private array<PipeRecord> pipeRecords; var private array<PipeRecord> pipeRecords;
@ -121,12 +123,20 @@ var private array<PipeRecord> pipeRecords;
// pipe bombs spawning). // pipe bombs spawning).
var private bool pipesRelevancyFlag; var private bool pipesRelevancyFlag;
var private Timer cleanupTimer;
protected function OnEnabled() protected function OnEnabled()
{ {
local LevelInfo level;
local PipeBombProjectile nextPipe; local PipeBombProjectile nextPipe;
pipesRelevancyFlag = class'PipeBombProjectile'.default.bAlwaysRelevant; pipesRelevancyFlag = class'PipeBombProjectile'.default.bAlwaysRelevant;
class'PipeBombProjectile'.default.bGameRelevant = false; 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 // Fix pipes that are already lying about on the map
level = _.unreal.GetLevel();
foreach level.DynamicActors(class'KFMod.PipeBombProjectile', nextPipe) { foreach level.DynamicActors(class'KFMod.PipeBombProjectile', nextPipe) {
RegisterPipe(nextPipe); RegisterPipe(nextPipe);
} }
@ -136,6 +146,7 @@ protected function OnDisabled()
{ {
local int i; local int i;
class'PipeBombProjectile'.default.bGameRelevant = pipesRelevancyFlag; class'PipeBombProjectile'.default.bGameRelevant = pipesRelevancyFlag;
cleanupTimer.FreeSelf();
for (i = 0; i < pipeRecords.length; i += 1) { for (i = 0; i < pipeRecords.length; i += 1) {
ReleasePipe(pipeRecords[i]); ReleasePipe(pipeRecords[i]);
} }
@ -154,19 +165,18 @@ public final function RegisterPipe(PipeBombProjectile newPipe)
// Check whether we have already added this pipe // Check whether we have already added this pipe
for (i = 0; i < pipeRecords.length; i += 1) for (i = 0; i < pipeRecords.length; i += 1)
{ {
if (pipeRecords[i].pipe == newPipe) { if (pipeRecords[i].pipe.Get() == newPipe) {
return; return;
} }
} }
newRecord.pipe = newPipe; newRecord.pipe = _.unreal.ActorRef(newPipe);
// Setup `PipesSafetyCollision` for catching `TakeDamage()` events // Setup `PipesSafetyCollision` for catching `TakeDamage()` events
// (only if we need to according to settings) // (only if we need to according to settings)
if (NeedSafetyCollision()) if (NeedSafetyCollision())
{ {
newRecord.safetyCollision = newRecord.safetyCollision = _.unreal.ActorRef(
class'PipesSafetyCollision'.static.ProtectPipes(newPipe); class'PipesSafetyCollision'.static.ProtectPipes(newPipe));
} }
newRecord.pipe = newPipe;
pipeRecords[pipeRecords.length] = newRecord; pipeRecords[pipeRecords.length] = newRecord;
// Intercept proximity checks (only if we need to according to settings) // Intercept proximity checks (only if we need to according to settings)
if (NeedManagedProximityChecks()) if (NeedManagedProximityChecks())
@ -181,16 +191,26 @@ public final function RegisterPipe(PipeBombProjectile newPipe)
// Rolls back our changes to the pipe in the given `PipeRecord`. // Rolls back our changes to the pipe in the given `PipeRecord`.
public final function ReleasePipe(PipeRecord pipeRecord) public final function ReleasePipe(PipeRecord pipeRecord)
{ {
local PipeBombProjectile pipe;
local PipesSafetyCollision safetyCollision;
if (pipeRecord.safetyCollision != none) if (pipeRecord.safetyCollision != none)
{ {
pipeRecord.safetyCollision.TurnOff(); safetyCollision =
PipesSafetyCollision(pipeRecord.safetyCollision.Get());
pipeRecord.safetyCollision.FreeSelf();
pipeRecord.safetyCollision = none; 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; pipeRecord.proximityCheckIntercepted = false;
if (IsPipeDoingProximityChecks(pipeRecord.pipe)) { if (IsPipeDoingProximityChecks(pipe)) {
pipeRecord.pipe.SetTimer(pipeRecord.timerCountDown, true); pipe.SetTimer(pipeRecord.timerCountDown, true);
} }
} }
} }
@ -215,12 +235,15 @@ private final function bool NeedManagedProximityChecks()
} }
// Removes dead records with pipe instances turned into `none` // Removes dead records with pipe instances turned into `none`
private final function CleanPipeRecords() private final function CleanPipeRecords(Timer source)
{ {
local int i; local int i;
while (i < pipeRecords.length) 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); pipeRecords.Remove(i, 1);
} }
else { else {
@ -236,13 +259,17 @@ private final function CleanPipeRecords()
private final function InterceptProximityChecks() private final function InterceptProximityChecks()
{ {
local int i; local int i;
local PipeBombProjectile nextPipe;
for (i = 0; i < pipeRecords.length; i += 1) for (i = 0; i < pipeRecords.length; i += 1)
{ {
if (pipeRecords[i].proximityCheckIntercepted) continue; if (pipeRecords[i].proximityCheckIntercepted) continue;
if (pipeRecords[i].pipe == none) continue; nextPipe = PipeBombProjectile(pipeRecords[i].pipe.Get());
if (IsPipeDoingProximityChecks(pipeRecords[i].pipe)) 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; // We set `1.0` because that is the vanilla value;
// Line 123 of "PipeBombProjectile.uc": `SetTimer(1.0,True);` // Line 123 of "PipeBombProjectile.uc": `SetTimer(1.0,True);`
pipeRecords[i].timerCountDown = 1.0; pipeRecords[i].timerCountDown = 1.0;
@ -269,17 +296,18 @@ private final function PerformProximityChecks(float delta)
{ {
local int i; local int i;
local Vector checkLocation; local Vector checkLocation;
local PipeBombProjectile nextPipe;
for (i = 0; i < pipeRecords.length; i += 1) 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 // `timerCountDown` does not makes sense for pipes that
// are not doing proxiity checks // are not doing proxiity checks
if (!IsPipeDoingProximityChecks(pipeRecords[i].pipe)) continue; if (!IsPipeDoingProximityChecks(nextPipe)) continue;
pipeRecords[i].timerCountDown -= delta; checkLocation = nextPipe.location;
if (pipeRecords[i].timerCountDown <= 0)
{
checkLocation = pipeRecords[i].pipe.location;
if (proximityCheckElevation > 0) { if (proximityCheckElevation > 0) {
checkLocation.z += proximityCheckElevation; checkLocation.z += proximityCheckElevation;
} }
@ -288,9 +316,7 @@ private final function PerformProximityChecks(float delta)
DoPipeProximityCheck(pipeRecords[i], checkLocation); DoPipeProximityCheck(pipeRecords[i], checkLocation);
} }
} }
}
// Assumes `pipeRecord.pipe != none`
// Original code is somewhat messy and was reworked in this more manageble // 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 // form as core logic is simple - for every nearby `Pawn` we increase the
// percieved level for the pipe and when it reaches certain threshold // percieved level for the pipe and when it reaches certain threshold
@ -319,11 +345,14 @@ private final function DoPipeProximityCheck(
local Pawn checkPawn; local Pawn checkPawn;
local float threatLevel; local float threatLevel;
local PipeBombProjectile pipe; local PipeBombProjectile pipe;
pipe = pipeRecord.pipe; pipe = PipeBombProjectile(pipeRecord.pipe.Get());
if (pipe == none) {
return;
}
pipe.bAlwaysRelevant = false; pipe.bAlwaysRelevant = false;
pipe.PlaySound(pipe.beepSound,, 0.5,, 50.0); pipe.PlaySound(pipe.beepSound,, 0.5,, 50.0);
// Out rewritten logic, which should do exactly the same: // Out rewritten logic, which should do exactly the same:
foreach VisibleCollidingActors( class'Pawn', checkPawn, foreach pipe.VisibleCollidingActors( class'Pawn', checkPawn,
pipe.detectionRadius, checkLocation) pipe.detectionRadius, checkLocation)
{ {
threatLevel += GetThreatLevel(pipe, checkPawn); threatLevel += GetThreatLevel(pipe, checkPawn);
@ -402,7 +431,6 @@ private final function float GetThreatLevel(
event Tick(float delta) event Tick(float delta)
{ {
CleanPipeRecords();
if (NeedManagedProximityChecks()) if (NeedManagedProximityChecks())
{ {
InterceptProximityChecks(); InterceptProximityChecks();

6
sources/FixProjectileFF/FixProjectileFF.uc

@ -134,16 +134,12 @@ public final static function class<ROBallisticProjectile> FindFixedClass(
// If `FixProjectileFF` in disabled always returns `false`. // If `FixProjectileFF` in disabled always returns `false`.
public final static function bool IsFriendlyFireAcceptable() public final static function bool IsFriendlyFireAcceptable()
{ {
local TeamGame gameType;
local FixProjectileFF projectileFFFix; local FixProjectileFF projectileFFFix;
projectileFFFix = FixProjectileFF(GetInstance()); projectileFFFix = FixProjectileFF(GetInstance());
if (projectileFFFix == none) return false; if (projectileFFFix == none) return false;
if (projectileFFFix.ignoreFriendlyFire) 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 defaultproperties

3
sources/FixProjectileFF/MutatorListener_FixProjectileFF.uc

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

44
sources/FixSpectatorCrash/FixSpectatorCrash.uc

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

45
sources/FixZedTimeLags/FixZedTimeLags.uc

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

Loading…
Cancel
Save