/** * This feature fixes lags caused by a zed time that can occur * on some maps when a lot of zeds are present at once. * As a side effect it also fixes an issue where during zed time speed up * `zedTimeSlomoScale` was assumed to be default value of `0.2`. * Now zed time will behave correctly with mods that * change `zedTimeSlomoScale`. * Copyright 2020 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 FixZedTimeLags_Feature extends Feature dependson(ConnectionService); /** * When zed time activates, game speed is immediately set to * `zedTimeSlomoScale` (0.2 by default), defined, like all other variables, * in `KFGameType`. Zed time lasts `zedTimeDuration` seconds (3.0 by default), * but during last `zedTimeDuration * 0.166` seconds (by default 0.498) * it starts to speed back up, causing game speed to update every tick. * This makes animations look more smooth when exiting zed-time; * however, updating speed every tick for that purpose seems like * an overkill and, combined with things like * increased tick rate, certain maps and raised zed limit, * it can lead to noticeable lags at the end of zed time. * To fix this issue we disable `Tick()` event in * `KFGameType` and then repeat that functionality in our own `Tick()` event, * but only perform game speed updates occasionally, * to make sure that overall amount of updates won't go over a limit, * that can be configured via `maxGameSpeedUpdatesAmount` * Author's test (looking really hard on clots' animations) * seem to suggest that there shouldn't be much visible difference if * we limit game speed updates to about 2 or 3. */ // Max amount of game speed updates during speed up phase // (actual amount of updates can't be larger than amount of ticks). // On servers with default 30 tick rate there's usually // about 13 updates total on vanilla game. // Values lower than 1 are treated like 1. var private config int maxGameSpeedUpdatesAmount; // [ADVANCED] Don't change this setting unless you know what you're doing. // Compatibility setting that allows to keep `GameInfo`'s `Tick()` event // from being disabled. // Useful when running Acedia along with custom `GameInfo` // (that isn't `KFGameType`) that relies on `Tick()` event. // Note, however, that in order to keep this fix working properly, // it's on you to make sure `KFGameType.Tick()` logic isn't executed. var private config bool disableTick; // Counts how much time is left until next update var private float updateCooldown; protected function OnEnabled() { if (disableTick) { _server.unreal.GetGameType().Disable('Tick'); } _server.unreal.OnTick(self).connect = Tick; } protected function OnDisabled() { if (disableTick) { _server.unreal.GetGameType().Enable('Tick'); } _server.unreal.OnTick(self).Disconnect(); } protected function SwapConfig(FeatureConfig config) { local FixZedTimeLags newConfig; newConfig = FixZedTimeLags(config); if (newConfig == none) { return; } maxGameSpeedUpdatesAmount = newConfig.maxGameSpeedUpdatesAmount; disableTick = newConfig.disableTick; } private function Tick(float delta, float timeDilationCoefficient) { local KFGameType gameType; local float trueTimePassed; gameType = _server.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 // (maybe native code?); only really matters during zed time. if (disableTick) { gameType.Disable('Tick'); } // How much real (not in-game) time has passed trueTimePassed = delta / timeDilationCoefficient; gameType.currentZEDTimeDuration -= trueTimePassed; // Handle speeding up phase if (gameType.bSpeedingBackUp) { DoSpeedBackUp(trueTimePassed, gameType); } else if (gameType.currentZEDTimeDuration < GetSpeedupDuration(gameType)) { gameType.bSpeedingBackUp = true; updateCooldown = GetFullUpdateCooldown(gameType); TellClientsZedTimeEnds(); DoSpeedBackUp(trueTimePassed, gameType); } // End zed time once it's duration has passed if (gameType.currentZEDTimeDuration <= 0) { gameType.bZEDTimeActive = false; gameType.bSpeedingBackUp = false; gameType.zedTimeExtensionsUsed = 0; gameType.SetGameSpeed(1.0); } } private final function TellClientsZedTimeEnds() { local int i; local KFPlayerController player; local ConnectionService service; local array connections; service = ConnectionService(class'ConnectionService'.static.GetInstance()); if (service == none) return; connections = service.GetActiveConnections(); for (i = 0; i < connections.length; i += 1) { player = KFPlayerController(connections[i].controllerReference); if (player != none) { // Play sound of leaving zed time player.ClientExitZedTime(); } } } // This function is called every tick during speed up phase and manages // gradual game speed increase. 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. // The rest of the updates will be uniformly distributed // over the speed up duration. local float newGameSpeed; local float slowdownScale; if (maxGameSpeedUpdatesAmount <= 1) return; if (updateCooldown > 0.0) { updateCooldown -= trueTimePassed; return; } else { updateCooldown = GetFullUpdateCooldown(gameType); } slowdownScale = gameType.currentZEDTimeDuration / GetSpeedupDuration(gameType); newGameSpeed = Lerp(slowdownScale, 1.0, gameType.zedTimeSlomoScale); gameType.SetGameSpeed(newGameSpeed); } private final function float GetSpeedupDuration(KFGameType gameType) { return gameType.zedTimeDuration * 0.166; } private final function float GetFullUpdateCooldown(KFGameType gameType) { return GetSpeedupDuration(gameType) / maxGameSpeedUpdatesAmount; } defaultproperties { configClass = class'FixZedTimeLags' maxGameSpeedUpdatesAmount = 3 disableTick = true }