/**
* 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) {
_.unreal.GetGameType().Disable('Tick');
}
_.unreal.OnTick(self).connect = Tick;
}
protected function OnDisabled()
{
if (disableTick) {
_.unreal.GetGameType().Enable('Tick');
}
_.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 = _.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
}