You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 

347 lines
14 KiB

/**
* Acedia currently lacks its own means to provide a map/mode voting
* (and new voting mod with proper GUI would not be whitelisted anyway).
* This is why this class was made - to inject existing voting handlers with
* data from Acedia's game modes.
* Requires `GameInfo`'s voting handler to be derived from
* `XVotingHandler`, which is satisfied by pretty much every used handler.
* 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 VotingHandlerAdapter extends AcediaObject
dependson(VotingHandler);
/**
* All usage of this object should start with `InjectIntoVotingHandler()`
* method that will read all the `GameMode` configs and fill voting handler's
* config with their data, while making a backup of all values.
* Backup can be restored with `RestoreVotingHandlerConfigBackup()` method.
* How that affects the clients depends on whether restoration was done before,
* during or after the replication. It is intended to be done after
* server travel has started.
* the process of injection is to create an ordered list of game modes
* (`availableGameModes`) and generate appropriate voting handler's configs
* with `BuildVotingHandlerConfig()`, saving them in the same order inside
* the voting handler. Picked game mode is then determined by index of
* the picked voting handler's option.
*
* Additionally this class has a static internal state that allows it to
* transfer data along the server travel - it is used mainly to remember picked
* game mode and enforce game's difficulty by altering and restoring
* `GameInfo`'s variable.
* To make such transfer happen one must call `PrepareForServerTravel()` before
* server travel to set the internal static state and
* then `SetupGameModeAfterTravel()` after travel (when the new map is loading)
* to read (and forget) from internal state.
*/
// Aliases are an unnecessary overkill for difficulty names, so just define
// them in special `string` arrays.
// We accept detect not just these exact words, but any of their prefixes.
var private const array<string> beginnerSynonyms;
var private const array<string> normalSynonyms;
var private const array<string> hardSynonyms;
var private const array<string> suicidalSynonyms;
var private const array<string> hoeSynonyms;
// All available game modes for Acedia, loaded during initialization.
// This array is directly produces replacement for `XVotingHandler`'s
// `gameConfig` array and records of `availableGameModes` relate to those of
// `gameConfig` with the same index.
// So if we know that a voting option with a certain index was chosen -
// it means that user picked game mode from `availableGameModes` with
// the same index.
var private array<Text> availableGameModes;
// Finding voting handler is not cheap, so only do it once and then store it.
var private NativeActorRef votingHandlerReference;
// Save `VotingHandler`'s config to restore it before server travel -
// otherwise Acedia will alter its config
var private array<VotingHandler.MapVoteGameConfig> backupVotingHandlerConfig;
// Setting default value of this flag to `true` indicates that map switching
// just occurred and we need to recover some information from the previous map.
var private bool isServerTraveling;
// We should not rely on "VotingHandler" to inform us from which game mode its
// selected config option originated after server travel, so we need to
// remember it in this default variable before switching maps.
var private string targetGameMode;
// Acedia's game modes intend on supporting difficulty switching, but
// `KFGameType` does not support appropriate flags, so we enforce default
// difficulty by overwriting default value of its `gameDifficulty` variable.
// But to not affect game's configs we must restore old value after new map is
// loaded. Store it in default variable for that.
var private float storedGameDifficulty;
var private LoggerAPI.Definition fatNoXVotingHandler, fatBadGameConfigIndexVH;
var private LoggerAPI.Definition fatBadGameConfigIndexAdapter;
protected function Finalizer()
{
_.memory.Free(votingHandlerReference);
_.memory.FreeMany(availableGameModes);
votingHandlerReference = none;
availableGameModes.length = 0;
}
/**
* Replaces `XVotingHandler`'s configs with Acedia's game modes.
* Backup of replaced configs is made internally, so that they can be restored
* on map change.
*/
public final function InjectIntoVotingHandler()
{
local int i;
local GameMode nextGameMode;
local XVotingHandler votingHandler;
local array<VotingHandler.MapVoteGameConfig> newVotingHandlerConfig;
if (votingHandlerReference != none) {
return;
}
votingHandler = XVotingHandler(_server.unreal.FindActorInstance(
_server.unreal.GetGameType().VotingHandlerClass));
if (votingHandler == none)
{
_.logger.Auto(fatNoXVotingHandler);
return;
}
votingHandlerReference = _server.unreal.ActorRef(votingHandler);
class'GameMode'.static.Initialize();
availableGameModes = class'GameMode'.static.AvailableConfigs();
for (i = 0; i < availableGameModes.length; i += 1)
{
nextGameMode = GameMode(class'GameMode'.static
.GetConfigInstance(availableGameModes[i]));
newVotingHandlerConfig[i] = BuildVotingHandlerConfig(nextGameMode);
// Report omitted mutators / server options
nextGameMode.ReportBadMutatorNames();
nextGameMode.ReportBadOptions();
}
backupVotingHandlerConfig = votingHandler.gameConfig;
votingHandler.gameConfig = newVotingHandlerConfig;
}
private function VotingHandler.MapVoteGameConfig BuildVotingHandlerConfig(
GameMode gameMode)
{
local VotingHandler.MapVoteGameConfig result;
result.gameClass = _.text.IntoString(gameMode.GetGameTypeClass());
result.gameName = _.text.ToColoredString(gameMode.GetTitle());
result.prefix = _.text.IntoString(gameMode.GetMapPrefix());
result.acronym = _.text.IntoString(gameMode.GetAcronym());
result.mutators = BuildMutatorString(gameMode);
result.options = BuildOptionsString(gameMode);
return result;
}
private function string BuildMutatorString(GameMode gameMode)
{
local int i;
local string result;
local array<Text> usedMutators;
usedMutators = gameMode.GetIncludedMutators();
for (i = 0; i < usedMutators.length; i += 1)
{
if (i > 0) {
result $= ",";
}
result $= _.text.IntoString(usedMutators[i]);
}
return result;
}
private function string BuildOptionsString(GameMode gameMode)
{
local bool optionWasAdded;
local string result;
local string nextKey, nextValue;
local CollectionIterator iter;
local HashTable options;
options = gameMode.GetOptions();
for (iter = options.Iterate(); !iter.HasFinished(); iter.Next())
{
nextKey = _.text.IntoString(Text(iter.GetKey()));
nextValue = _.text.IntoString(Text(iter.Get()));
if (optionWasAdded) {
result $= "?";
}
result $= (nextKey $ "=" $ nextValue);
optionWasAdded = true;
}
options.FreeSelf();
iter.FreeSelf();
return result;
}
/**
* Makes necessary preparations for the server travel.
*/
public final function PrepareForServerTravel()
{
local int pickedVHConfig;
local GameMode nextGameMode;
local string nextGameClassName;
local class<GameInfo> nextGameClass;
local XVotingHandler votingHandler;
if (votingHandlerReference == none) return;
votingHandler = XVotingHandler(votingHandlerReference.Get());
if (votingHandler == none) return;
// Server travel caused by something else than `XVotingHandler`
if (!votingHandler.bLevelSwitchPending) return;
pickedVHConfig = votingHandler.currentGameConfig;
if (pickedVHConfig < 0 || pickedVHConfig >= votingHandler.gameConfig.length)
{
_.logger.Auto(fatBadGameConfigIndexVH)
.ArgInt(pickedVHConfig)
.ArgInt(votingHandler.gameConfig.length);
return;
}
if (pickedVHConfig >= availableGameModes.length)
{
_.logger.Auto(fatBadGameConfigIndexAdapter)
.ArgInt(pickedVHConfig)
.ArgInt(availableGameModes.length);
return;
}
nextGameClassName = votingHandler.gameConfig[pickedVHConfig].gameClass;
if (string(_server.unreal.GetGameType().class) ~= nextGameClassName) {
nextGameClass = _server.unreal.GetGameType().class;
}
else
{
nextGameClass =
class<GameInfo>(_.memory.LoadClass_S(nextGameClassName));
}
default.isServerTraveling = true;
default.targetGameMode = availableGameModes[pickedVHConfig].ToString();
nextGameMode = GetConfigFromString(default.targetGameMode);
default.storedGameDifficulty = nextGameClass.default.gameDifficulty;
nextGameClass.default.gameDifficulty = GetNumericDifficulty(nextGameMode);
}
/**
* Restore `GameInfo`'s settings after the server travel and
* apply selected `GameMode`.
*
* @return `GameMode` picked before server travel
* (the one that must be running now).
*/
public final function GameMode SetupGameModeAfterTravel()
{
if (!default.isServerTraveling) {
return none;
}
_server.unreal.GetGameType().default.gameDifficulty =
default.storedGameDifficulty;
default.isServerTraveling = false;
return GetConfigFromString(targetGameMode);
}
/**
* Restores `XVotingHandler`'s config to the values that were overridden by
* `VHAdapter`'s `InjectIntoVotingHandler()` method.
*/
public final function RestoreVotingHandlerConfigBackup()
{
local XVotingHandler votingHandler;
if (votingHandlerReference == none) return;
votingHandler = XVotingHandler(votingHandlerReference.Get());
if (votingHandler == none) return;
votingHandler.gameConfig = backupVotingHandlerConfig;
votingHandler.default.gameConfig = backupVotingHandlerConfig;
votingHandler.SaveConfig();
}
// `GameMode`'s name as a `string` -> `GameMode` instance
private function GameMode GetConfigFromString(string configName)
{
local GameMode result;
local Text nextConfigName;
nextConfigName = _.text.FromString(configName);
result = GameMode(class'GameMode'.static.GetConfigInstance(nextConfigName));
_.memory.Free(nextConfigName);
return result;
}
// Convert `GameMode`'s difficulty's textual representation into
// KF's numeric one.
private final function int GetNumericDifficulty(GameMode gameMode)
{
local int i;
local string difficulty;
difficulty = Locs(_.text.IntoString(gameMode.GetDifficulty()));
for (i = 0; i < default.beginnerSynonyms.length; i += 1)
{
if (IsPrefixOf(difficulty, default.beginnerSynonyms[i])) {
return 1;
}
}
for (i = 0; i < default.normalSynonyms.length; i += 1)
{
if (IsPrefixOf(difficulty, default.normalSynonyms[i])) {
return 2;
}
}
for (i = 0; i < default.hardSynonyms.length; i += 1)
{
if (IsPrefixOf(difficulty, default.hardSynonyms[i])) {
return 4;
}
}
for (i = 0; i < default.suicidalSynonyms.length; i += 1)
{
if (IsPrefixOf(difficulty, default.suicidalSynonyms[i])) {
return 5;
}
}
for (i = 0; i < default.hoeSynonyms.length; i += 1)
{
if (IsPrefixOf(difficulty, default.hoeSynonyms[i])) {
return 7;
}
}
return int(difficulty);
}
protected final static function bool IsPrefixOf(string prefix, string value)
{
return (InStr(value, prefix) == 0);
}
defaultproperties
{
beginnerSynonyms(0) = "easy"
beginnerSynonyms(1) = "beginer"
beginnerSynonyms(2) = "beginner"
beginnerSynonyms(3) = "begginer"
beginnerSynonyms(4) = "begginner"
normalSynonyms(0) = "regular"
normalSynonyms(1) = "default"
normalSynonyms(2) = "normal"
hardSynonyms(0) = "harder" // "hard" is prefix of this, so it will count
hardSynonyms(1) = "difficult"
suicidalSynonyms(0) = "suicidal"
hoeSynonyms(0) = "hellonearth"
hoeSynonyms(1) = "hellon earth"
hoeSynonyms(2) = "hell onearth"
hoeSynonyms(3) = "hoe"
fatNoXVotingHandler = (l=LOG_Fatal,m="`XVotingHandler` class is missing. Make sure your server setup supports Acedia's game modes (by used voting handler derived from `XVotingHandler`).")
fatBadGameConfigIndexVH = (l=LOG_Fatal,m="`XVotingHandler`'s `currentGameConfig` variable value of %1 is out-of-bounds for `XVotingHandler.gameConfig` of length %2. Report this issue.")
fatBadGameConfigIndexAdapter = (l=LOG_Fatal,m="`XVotingHandler`'s `currentGameConfig` variable value of %1 is out-of-bounds for `VHAdapter` of length %2. Report this issue.")
}