diff --git a/config/AcediaFixes.ini b/config/AcediaFixes.ini index ec3b328..227a96c 100644 --- a/config/AcediaFixes.ini +++ b/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] diff --git a/sources/FixAmmoSelling/FixAmmoSelling.uc b/sources/FixAmmoSelling/FixAmmoSelling.uc index c32ed41..d9a942b 100644 --- a/sources/FixAmmoSelling/FixAmmoSelling.uc +++ b/sources/FixAmmoSelling/FixAmmoSelling.uc @@ -131,13 +131,12 @@ struct WeaponRecord var int lastAmmoAmount; }; -// All weapons we've detected so far. -var private array 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 stalkers; + local array 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 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 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 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' } \ No newline at end of file diff --git a/sources/FixAmmoSelling/FixAmmoSellingService.uc b/sources/FixAmmoSelling/FixAmmoSellingService.uc new file mode 100644 index 0000000..b8c8857 --- /dev/null +++ b/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 . + */ +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 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 +{ +} \ No newline at end of file diff --git a/sources/FixDoshSpam/FixDoshSpam.uc b/sources/FixDoshSpam/FixDoshSpam.uc index ced8928..73b9a84 100644 --- a/sources/FixDoshSpam/FixDoshSpam.uc +++ b/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 currentContributors; // Wads of cash that are lying around on the map. -var private array wads; +var private array 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' } \ No newline at end of file diff --git a/sources/FixDualiesCost/DualiesCostRule.uc b/sources/FixDualiesCost/DualiesCostRule.uc deleted file mode 100644 index 1009388..0000000 --- a/sources/FixDualiesCost/DualiesCostRule.uc +++ /dev/null @@ -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 . - */ -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 -{ -} \ No newline at end of file diff --git a/sources/FixDualiesCost/FixDualiesCost.uc b/sources/FixDualiesCost/FixDualiesCost.uc index e5c9114..93da39c 100644 --- a/sources/FixDualiesCost/FixDualiesCost.uc +++ b/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 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 pendingValues; @@ -133,13 +135,15 @@ var private const array pendingValues; // Describe sell values of all currently existing single pistols. struct WeaponDataRecord { - var KFWeapon reference; + // Reference to `KFWeapon` instance + var NativeActorRef reference; var class 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 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(); diff --git a/sources/FixFFHack/FFHackRule.uc b/sources/FixFFHack/FFHackRule.uc deleted file mode 100644 index 090f723..0000000 --- a/sources/FixFFHack/FFHackRule.uc +++ /dev/null @@ -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 . - */ -class FFHackRule extends GameRules; - -function int NetDamage( - int originalDamage, - int damage, - Pawn injured, - Pawn instigator, - Vector hitLocation, - out Vector momentum, - class 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 -{ -} \ No newline at end of file diff --git a/sources/FixFFHack/FixFFHack.uc b/sources/FixFFHack/FixFFHack.uc index efabd60..37d3f2c 100644 --- a/sources/FixFFHack/FixFFHack.uc +++ b/sources/FixFFHack/FixFFHack.uc @@ -60,12 +60,54 @@ var private config const array< class > 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) +{ + // 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 diff --git a/sources/FixInfiniteNades/FixInfiniteNades.uc b/sources/FixInfiniteNades/FixInfiniteNades.uc index 3fcbbb7..3d46a6e 100644 --- a/sources/FixInfiniteNades/FixInfiniteNades.uc +++ b/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 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 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); } } diff --git a/sources/FixInfiniteNades/FixedFragFire.uc b/sources/FixInfiniteNades/FixedFragFire.uc index 24342cc..702696f 100644 --- a/sources/FixInfiniteNades/FixedFragFire.uc +++ b/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(); } } diff --git a/sources/FixInfiniteNades/MutatorListener_FixInfiniteNades.uc b/sources/FixInfiniteNades/MutatorListener_FixInfiniteNades.uc index 0a1aec1..255b1c7 100644 --- a/sources/FixInfiniteNades/MutatorListener_FixInfiniteNades.uc +++ b/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); diff --git a/sources/FixInventoryAbuse/FixInventoryAbuse.uc b/sources/FixInventoryAbuse/FixInventoryAbuse.uc index 7414e90..083f8fe 100644 --- a/sources/FixInventoryAbuse/FixInventoryAbuse.uc +++ b/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 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 GetRootPickupClass(KFWeapon weapon) if (weapon == none) return none; // Start with a pickup of the given weapons root = class(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; diff --git a/sources/FixLogSpam/SpamPickup/HelperPickup.uc b/sources/FixLogSpam/SpamPickup/HelperPickup.uc index 363f7cc..57409bd 100644 --- a/sources/FixLogSpam/SpamPickup/HelperPickup.uc +++ b/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 recordedPickups; +var private array recordedPickups; // Pickups that will get `bDropped` flag checked the next tick to // determine if we need to fix them. -var private array pendingPickups; +var private array 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(); diff --git a/sources/FixLogSpam/SpamPickup/PickupSpamRule.uc b/sources/FixLogSpam/SpamPickup/PickupSpamRule.uc deleted file mode 100644 index f2fd8b7..0000000 --- a/sources/FixLogSpam/SpamPickup/PickupSpamRule.uc +++ /dev/null @@ -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 . - */ -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 -{ -} \ No newline at end of file diff --git a/sources/FixLogSpam/SpamTrader/HelperTrader.uc b/sources/FixLogSpam/SpamTrader/HelperTrader.uc index 2d3467a..b95e3cf 100644 --- a/sources/FixLogSpam/SpamTrader/HelperTrader.uc +++ b/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 fixedShops; +// Reference tp `ShopVolume`. +var private array 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() diff --git a/sources/FixPipes/FixPipes.uc b/sources/FixPipes/FixPipes.uc index 35c4a26..e5a88e1 100644 --- a/sources/FixPipes/FixPipes.uc +++ b/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 pipeRecords; @@ -121,12 +123,20 @@ var private array 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(); diff --git a/sources/FixProjectileFF/FixProjectileFF.uc b/sources/FixProjectileFF/FixProjectileFF.uc index 78158e7..3a2c277 100644 --- a/sources/FixProjectileFF/FixProjectileFF.uc +++ b/sources/FixProjectileFF/FixProjectileFF.uc @@ -134,16 +134,12 @@ public final static function class 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 diff --git a/sources/FixProjectileFF/MutatorListener_FixProjectileFF.uc b/sources/FixProjectileFF/MutatorListener_FixProjectileFF.uc index 3f98d12..95ba8d0 100644 --- a/sources/FixProjectileFF/MutatorListener_FixProjectileFF.uc +++ b/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; } diff --git a/sources/FixSpectatorCrash/FixSpectatorCrash.uc b/sources/FixSpectatorCrash/FixSpectatorCrash.uc index 25f54fa..838c7ae 100644 --- a/sources/FixSpectatorCrash/FixSpectatorCrash.uc +++ b/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 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 violators; +// References to `PlayerController`s. +var private array 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); } diff --git a/sources/FixZedTimeLags/FixZedTimeLags.uc b/sources/FixZedTimeLags/FixZedTimeLags.uc index f78fe83..7813cc7 100644 --- a/sources/FixZedTimeLags/FixZedTimeLags.uc +++ b/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