Compare commits

..

No commits in common. '9231e79312c93274fd92cdd2fb7eebcf917406c1' and '76fdcc46de27628b2c2db16247520c1bb64d01bb' have entirely different histories.

  1. 43
      config/AcediaGameModes.ini
  2. 39
      config/AcediaMaps.ini
  3. 3
      sources/AcediaLauncherMut.uc
  4. 27
      sources/GameModes/BaseGameMode.uc
  5. 20
      sources/GameModes/GameMode.uc
  6. 99
      sources/MapList/MapList.uc
  7. 346
      sources/MapList/MapTool.uc
  8. 4
      sources/StartUp.uc
  9. 47
      sources/VotingHandlerAdapter.uc

43
config/AcediaGameModes.ini

@ -1,48 +1,7 @@
[hard GameMode] [hard GameMode]
;= Add a section like this one for every voting option title={$green Hard difficulty}
;=
;= `title` will be displayed in a drop-down list in the voting dialog
title={$green Hard}
;= `difficulty` determines... difficulty and for vanilla Killing Floor can be one of
;= the following values:
;=
;= * Beginner: "easy", "beginner"
;= * Normal: "normal", "default", "regular"
;= * Hard: "harder"
;= * Suicidal: "suicidal"
;= * Hell On Earth: "hellonearth", "hell on earth", "hoe"
;=
;= Any prefixes will also work, e.g. "sui", "hard", etc.
difficulty=hard difficulty=hard
;= How long should game last? For vanilla Killing Floor its "short",
;= "normal" (also "default" and "regular"), "long".
length=long
;= A short game mode name that will be displayed after map vote has finished
acronym={$green hard}
;= Use this to add map lists (from "AcediaMaps.ini") to this game mode
;= NOTE: map lists, NOT maps
includeMaps=default
;= Use this to add mutators to this game mode, one mutator per line
;includeMutator=
;= Use this do add one of Acedia's Features into this game mode with "default" config
;includeFeature=
;= Use this to add feature with specified config
;includeFeatureAs=(feature=,config=)
;= Use this to disable one of the Acedia's features for this game mode.
;= This overrides all other settings.
;= Purpose of this setting is to disable auto-enabled features.
;excludeFeature=
[sui GameMode]
title={$orange Suicidal}
difficulty=suicidal
length=long
acronym={$orange sui}
includeMaps=default
[hell GameMode] [hell GameMode]
title={$crimson Hell On Earth} title={$crimson Hell On Earth}
difficulty=hoe difficulty=hoe
length=long
acronym={$crimson hoe}
includeMaps=default

39
config/AcediaMaps.ini

@ -1,39 +0,0 @@
[default MapList]
map=KF-AbusementPark
map=KF-Aperture
map=KF-Bedlam
map=KF-Biohazard
map=KF-BioticsLab
map=KF-Clandestine
map=KF-Crash
map=KF-Departed
map=KF-EvilSantasLair
map=KF-Farm
map=KF-FilthsCross
map=KF-Forgotten
map=KF-Foundry
map=KF-FrightYard
map=KF-Hell
map=KF-Hellride
map=KF-HillbillyHorror
map=KF-Hospitalhorrors
map=KF-Icebreaker
map=KF-IceCave
map=KF-Manor
map=KF-MoonBase
map=KF-MountainPass
map=KF-Offices
map=KF-SirensBelch
map=KF-Steamland
map=KF-Stronghold
map=KF-Suburbia
map=KF-ThrillsChills
map=KF-Transit
map=KF-Waterworks
map=KF-WestLondon
map=KF-Wyre
[objective MapList]
map=KFO-FrightYard
map=KFO-Steamland
map=KFO-Transit

3
sources/AcediaLauncherMut.uc

@ -1,8 +1,7 @@
/** /**
* Main and only Acedia mutator. Used for providing access to mutator * Main and only Acedia mutator. Used for providing access to mutator
* events' calls and detecting server travel. * events' calls and detecting server travel.
* Copyright 2020-2023 Anton Tarasenko * Copyright 2020-2022 Anton Tarasenko
* 2023 Shtoyan
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *

27
sources/GameModes/BaseGameMode.uc

@ -10,7 +10,7 @@
* be used based on game info's settings; * be used based on game info's settings;
* 3. `Report...()` methods that perform various validation checks * 3. `Report...()` methods that perform various validation checks
* (and log them) on config data. * (and log them) on config data.
* Copyright 2021-2023 Anton Tarasenko * Copyright 2021-2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -44,8 +44,6 @@ var protected config array<string> includeFeature;
// `Feature`s to exclude from game mode, regardless of other settings // `Feature`s to exclude from game mode, regardless of other settings
// (this one has highest priority) // (this one has highest priority)
var protected config array<string> excludeFeature; var protected config array<string> excludeFeature;
// Lists of maps to include for this game mode
var protected config array<string> includeMaps;
struct FeatureConfigPair struct FeatureConfigPair
{ {
@ -77,23 +75,17 @@ protected function HashTable ToData()
_.memory.Free(nextArray); _.memory.Free(nextArray);
nextArray = _.collections.EmptyArrayList(); nextArray = _.collections.EmptyArrayList();
for (i = 0; i < excludeFeature.length; i += 1) { for (i = 0; i < excludeFeature.length; i += 1) {
nextArray.AddString(excludeFeature[i]); nextArray.AddItem(_.text.FromString(excludeFeature[i]));
} }
result.SetItem(P("excludeFeature"), nextArray); result.SetItem(P("excludeFeature"), nextArray);
_.memory.Free(nextArray); _.memory.Free(nextArray);
nextArray = _.collections.EmptyArrayList(); nextArray = _.collections.EmptyArrayList();
for (i = 0; i < includeMutator.length; i += 1) { for (i = 0; i < includeMutator.length; i += 1) {
nextArray.AddString(includeFeature[i]); nextArray.AddItem(_.text.FromString(includeFeature[i]));
} }
result.SetItem(P("includeMutator"), nextArray); result.SetItem(P("includeMutator"), nextArray);
_.memory.Free(nextArray); _.memory.Free(nextArray);
nextArray = _.collections.EmptyArrayList(); nextArray = _.collections.EmptyArrayList();
for (i = 0; i < includeMaps.length; i += 1) {
nextArray.AddString(includeMaps[i]);
}
result.SetItem(P("includeMaps"), nextArray);
_.memory.Free(nextArray);
nextArray = _.collections.EmptyArrayList();
for (i = 0; i < includeFeatureAs.length; i += 1) for (i = 0; i < includeFeatureAs.length; i += 1)
{ {
nextPair = _.collections.EmptyHashTable(); nextPair = _.collections.EmptyHashTable();
@ -128,9 +120,6 @@ protected function FromData(HashTable source)
nextArray = source.GetArrayList(P("includeMutator")); nextArray = source.GetArrayList(P("includeMutator"));
includeMutator = DynamicIntoStringArray(nextArray); includeMutator = DynamicIntoStringArray(nextArray);
_.memory.Free(nextArray); _.memory.Free(nextArray);
nextArray = source.GetArrayList(P("includeMaps"));
includeMaps = DynamicIntoStringArray(nextArray);
_.memory.Free(nextArray);
nextArray = source.GetArrayList(P("includeFeatureAs")); nextArray = source.GetArrayList(P("includeFeatureAs"));
if (nextArray == none) { if (nextArray == none) {
return; return;
@ -431,16 +420,6 @@ public function array<Text> GetIncludedMutators()
return StringToTextArray(validatedMutators); return StringToTextArray(validatedMutators);
} }
public function array<Text> GetIncludedMapLists()
{
return StringToTextArray(includeMaps);
}
public function array<string> GetIncludedMapLists_S()
{
return includeMaps;
}
defaultproperties defaultproperties
{ {
configName = "AcediaGameModes" configName = "AcediaGameModes"

20
sources/GameModes/GameMode.uc

@ -1,7 +1,7 @@
/** /**
* The only implementation for `BaseGameMode` suitable for standard * The only implementation for `BaseGameMode` suitable for standard
* killing floor game types. * killing floor game types.
* Copyright 2021-2023 Anton Tarasenko * Copyright 2021-2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -35,6 +35,9 @@ var protected config string gameTypeClass;
// Short version of the name of the game mode players will see in // Short version of the name of the game mode players will see in
// voting handler messages sometimes (plain string) // voting handler messages sometimes (plain string)
var protected config string acronym; var protected config string acronym;
// Map prefix - only maps that start with specified prefix will be voteable for
// this game mode (plain string)
var protected config string mapPrefix;
// Aliases are an unnecessary overkill for difficulty names, so just define // Aliases are an unnecessary overkill for difficulty names, so just define
// them in special `string` arrays. // them in special `string` arrays.
@ -54,6 +57,7 @@ protected function DefaultIt()
difficulty = "Hell On Earth"; difficulty = "Hell On Earth";
gameTypeClass = "KFMod.KFGameType"; gameTypeClass = "KFMod.KFGameType";
acronym = ""; acronym = "";
mapPrefix = "KF";
includeFeature.length = 0; includeFeature.length = 0;
excludeFeature.length = 0; excludeFeature.length = 0;
includeMutator.length = 0; includeMutator.length = 0;
@ -72,7 +76,7 @@ protected function HashTable ToData()
} }
result.SetString(P("gameTypeClass"), gameTypeClass); result.SetString(P("gameTypeClass"), gameTypeClass);
result.SetString(P("acronym"), acronym); result.SetString(P("acronym"), acronym);
result.SetString(P("mapPrefix"), mapPrefix);
nextArray = _.collections.EmptyArrayList(); nextArray = _.collections.EmptyArrayList();
for (i = 0; i < option.length; i += 1) for (i = 0; i < option.length; i += 1)
{ {
@ -100,7 +104,7 @@ protected function FromData(HashTable source)
} }
gameTypeClass = source.GetString(P("gameTypeClass")); gameTypeClass = source.GetString(P("gameTypeClass"));
acronym = source.GetString(P("acronym")); acronym = source.GetString(P("acronym"));
mapPrefix = source.GetString(P("mapPrefix"));
nextArray = source.GetArrayList(P("option")); nextArray = source.GetArrayList(P("option"));
if (nextArray == none) { if (nextArray == none) {
return; return;
@ -140,6 +144,16 @@ public function Text GetAcronym()
} }
} }
public function Text GetMapPrefix()
{
if (mapPrefix == "") {
return _.text.FromString("KF-");
}
else {
return _.text.FromString(mapPrefix);
}
}
/** /**
* Checks option-related settings (`option`) for correctness and reports * Checks option-related settings (`option`) for correctness and reports
* any issues. * any issues.

99
sources/MapList/MapList.uc

@ -1,99 +0,0 @@
/**
* Config class for storing map lists.
* Copyright 2023 Anton Tarasenko
* 2023 Shtoyan
*------------------------------------------------------------------------------
* 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 MapList extends AcediaConfig
perObjectConfig
config(AcediaMaps);
var public config array<string> map;
protected function HashTable ToData() {
local int i;
local ArrayList mapArray;
local HashTable result;
result = _.collections.EmptyHashTable();
mapArray = _.collections.EmptyArrayList();
for (i = 0; i < map.length; i += 1) {
mapArray.AddString(map[i]);
}
result.SetItem(P("maps"), mapArray);
_.memory.Free(mapArray);
return result;
}
protected function FromData(HashTable source) {
local int i;
local ArrayList mapArray;
if (source == none) {
return;
}
mapArray = source.GetArrayList(P("maps"));
if (mapArray == none) {
return;
}
map.length = 0;
for (i = 0; i < mapArray.GetLength(); i += 1) {
map[map.length] = mapArray.GetString(i);
}
_.memory.Free(mapArray);
}
protected function DefaultIt() {
map.length = 0;
map[0] = "KF-AbusementPark";
map[1] = "KF-Aperture";
map[2] = "KF-Bedlam";
map[3] = "KF-Biohazard";
map[4] = "KF-BioticsLab";
map[5] = "KF-Clandestine";
map[6] = "KF-Crash";
map[7] = "KF-Departed";
map[8] = "KF-EvilSantasLair";
map[9] = "KF-Farm";
map[10] = "KF-FilthsCross";
map[11] = "KF-Forgotten";
map[12] = "KF-Foundry";
map[13] = "KF-FrightYard";
map[14] = "KF-Hell";
map[15] = "KF-Hellride";
map[16] = "KF-HillbillyHorror";
map[17] = "KF-Hospitalhorrors";
map[18] = "KF-Icebreaker";
map[19] = "KF-IceCave";
map[20] = "KF-Manor";
map[21] = "KF-MoonBase";
map[22] = "KF-MountainPass";
map[23] = "KF-Offices";
map[24] = "KF-SirensBelch";
map[25] = "KF-Steamland";
map[26] = "KF-Stronghold";
map[27] = "KF-Suburbia";
map[28] = "KF-ThrillsChills";
map[29] = "KF-Transit";
map[30] = "KF-Waterworks";
map[31] = "KF-WestLondon";
map[32] = "KF-Wyre";
}
defaultproperties {
configName = "AcediaMaps"
}

346
sources/MapList/MapTool.uc

@ -1,346 +0,0 @@
/**
* Author: Anton Tarasenko
* Home repo: https://insultplayers.ru/git/AcediaFramework/AcediaCore/
* License: GPL
* Copyright 2023 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 MapTool extends AcediaObject;
//! Tool for adapting AcediaLauncher's map lists for [`xVotingHandler`].
//!
//! This class is responsible for making [`xVotingHandler`] use specific maps (defined in
//! AcediaLauncer's configs) for specific game modes.
//! To achieve that it abuses [`xVotingHandler`]'s ability to filter maps by
//! game mode-specific prefix.
//! Normally prefix filtering for [`xVotingHandler`] is of limited use, because most Killing Floor
//! maps start with the same prefix `KF-` (and some objective ones starting with `KFO-`).
//! However we swap that prefix for something unique for each game mode: `MapSet0-`, `MapSet1-`,
//! etc, allowing us to pick the precise map set we want.
//!
//! There's two main challenges:
//!
//! 1. *Altered map names break voting* - since [`xVotingHandler`] expects to be provided real map
//! names and our mangled ones. We deal with it by catching a map change message broadcasted
//! right before actual map change occurs and swap our names with real ones.
//! 2. *Increased amount of maps to replicate* - if we implement this name mangling by using
//! a naive approach, in which we separately add maps for every game mode, then it will lead to
//! drastic increase in replication time of the complete map list to players.
//! Consider, for example, that you have 10 different game modes with exactly the same maps:
//! we will be needlessly replicating the exact same thing 10 times!
//! To solve this issue we specifically track map lists other game modes use, along with
//! prefixes assigned to them, and reuse already added maps in case two game modes are defined
//! to use the exactly same ones.
/// For storing which map sequences have which prefixes. Storage order is important.
struct MapSequenceRecord {
var public array<string> sequence;
var public string prefix;
};
// To avoid doing excesive work when injecting maps for a second time
var private bool injectedMaps;
// Finding voting handler is not cheap, so only do it once and then store it.
var private NativeActorRef votingHandlerReference;
// Resulting full map list with pseudonim (with replaced prefixes) and real names of maps.
var private array<VotingHandler.MapVoteMapList> pseudonimMapList;
var private array<VotingHandler.MapVoteMapList> realMapList;
// Map sequences used by game modes we've seen so far.
var private array<MapSequenceRecord> usedMapSequences;
// To more easily detect broadcasted message about map change we replace it with our own that is
// both unlikely to occur and is easy to get voted map name from.
var private string backupMessageMapWon;
var private string backupMessageAdminMapChange;
var private const string ACEDIA_ADMIN_MAP_CHANGE_COMMAND;
var private const string ACEDIA_MAP_WON_COMMAND;
var private LoggerAPI.Definition fatVotingHandlerMissing, warnMissingMapList;
protected function Finalizer() {
_server.unreal.broadcasts.OnHandleText(self).Disconnect();
_.memory.Free(votingHandlerReference);
votingHandlerReference = none;
pseudonimMapList.length = 0;
realMapList.length = 0;
usedMapSequences.length = 0;
injectedMaps = false;
}
/// Initializes [`MapTool`] by associating it with an [`XVotingHandler`].
///
/// Initialization fails if [`initVotingHandlerReference`] doesn't provide reference to
/// [`XVotingHandler`] or caller [`MapTool`] already was initialized.
/// Returns `true` iff initialization was successful.
public final function bool Initialize(NativeActorRef initVotingHandlerReference) {
if (initVotingHandlerReference == none) return false;
if (XVotingHandler(initVotingHandlerReference.Get()) == none) return false;
initVotingHandlerReference.NewRef();
votingHandlerReference = initVotingHandlerReference;
return true;
}
/// Adds map information from the new game mode.
///
/// Returns prefix that given game mode must use to display maps configured for it.
public final function string AddGameMode(GameMode gameMode) {
local XVotingHandler votingHandler;
local string gameModePrefix;
votingHandler = GetVotingHandler();
if (votingHandler == none) {
_.logger.Auto(fatVotingHandlerMissing);
return "KF";
}
if (CheckNeedToLoadMaps(gameMode, gameModePrefix)) {
LoadGameModeMaps(gameMode, gameModePrefix, votingHandler);
}
return gameModePrefix;
}
/// Injects final map list into [`XVotingHandler`].
///
/// Call this after all game modes have been added.
/// Shouldn't be called more than once.
public final function Inject() {
local XVotingHandler votingHandler;
votingHandler = GetVotingHandler();
if (votingHandler == none) {
_.logger.Auto(fatVotingHandlerMissing);
return;
}
votingHandler.mapList = pseudonimMapList;
votingHandler.mapCount = pseudonimMapList.length;
// Replace map change messages with our commands and make sure it is done only once,
// in case we mess up somewhere else and call this method second time
if (!injectedMaps) {
backupMessageMapWon = votingHandler.lmsgMapWon;
backupMessageAdminMapChange = votingHandler.lmsgAdminMapChange;
votingHandler.lmsgMapWon = ACEDIA_MAP_WON_COMMAND $ "::%mapname%";
votingHandler.lmsgAdminMapChange = ACEDIA_ADMIN_MAP_CHANGE_COMMAND $ "::%mapname%";
_server.unreal.broadcasts.OnHandleText(self).connect = HandleMapChange;
}
injectedMaps = true;
}
/// Builds arrays of [`VotingHandler::MapVoteMapList`] (each such item describes a map +
/// its meta data in a way [`XVotingHandler`] understands).
private function string LoadGameModeMaps(
GameMode gameMode,
string gameModePrefix,
XVotingHandler votingHandler
) {
local int i;
local ArrayList gameModeMaps;
local Text mapNameReal, mapNamePseudonim;
local VotingHandler.MapHistoryInfo nextMapInfo;
local VotingHandler.MapVoteMapList nextRecord;
local array<VotingHandler.MapVoteMapList> newMapsPseudonim, newMapReal;
nextRecord.bEnabled = true;
gameModeMaps = GetAllGameModeMaps(gameMode);
for (i = 0; i < gameModeMaps.GetLength(); i += 1) {
mapNameReal = gameModeMaps.GetText(i);
mapNamePseudonim = MakeMapPseudonim(mapNameReal, gameModePrefix);
if (votingHandler.history != none) {
nextMapInfo = votingHandler.history.GetMapHistory(mapNameReal.ToString());
nextRecord.playCount = nextMapInfo.p;
nextRecord.sequence = nextMapInfo.s;
}
nextRecord.mapName = _.text.IntoString(mapNamePseudonim);
newMapsPseudonim[newMapsPseudonim.length] = nextRecord;
nextRecord.mapName = _.text.IntoString(mapNameReal);
newMapReal[newMapReal.length] = nextRecord;
}
AppendMapsIntoVotingHandler(newMapsPseudonim, newMapReal);
_.memory.Free(gameModeMaps);
return gameModePrefix;
}
private function bool CheckNeedToLoadMaps(GameMode gameMode, out string prefix) {
local int mapSequenceIndex, mapListIndex;
local bool sameMapList, foundMatch;
local array<string> existingMapSequence, newMapSequence;
local MapSequenceRecord newRecord;
// We don't need to load maps for the `gameMode` only when we've already added the exactly same
// map sequence, order being important
newMapSequence = gameMode.GetIncludedMapLists_S();
for (mapSequenceIndex = 0; mapSequenceIndex < usedMapSequences.length; mapSequenceIndex += 1) {
existingMapSequence = usedMapSequences[mapSequenceIndex].sequence;
if (existingMapSequence.length != newMapSequence.length) {
continue;
}
foundMatch = true;
for (mapListIndex = 0; mapListIndex < newMapSequence.length; mapListIndex += 1) {
// Map lists are ASCII config names, so we can compare them with case-ignoring
// built-in `~=` operator works (it can only handle properly ASCII input)
sameMapList = (existingMapSequence[mapListIndex] ~= newMapSequence[mapListIndex]);
if (!sameMapList) {
foundMatch = false;
break;
}
}
if (foundMatch) {
prefix = usedMapSequences[mapSequenceIndex].prefix;
return false;
}
}
newRecord.sequence = newMapSequence;
newRecord.prefix = "MapSet" $ usedMapSequences.length;
usedMapSequences[usedMapSequences.length] = newRecord;
prefix = newRecord.prefix;
return true;
}
// Replaces prefixes like "KF-", "KFO-" or "KFS-" with "{gameModePrefix}-".
private function Text MakeMapPseudonim(Text realName, string gameModePrefix) {
local Parser parser;
local MutableText prefix, nameBody;
local MutableText result;
result = _.text.FromStringM(gameModePrefix);
result.Append(P("-"));
parser = realName.Parse();
parser.MUntil(prefix, _.text.GetCharacter("-"));
parser.Match(P("-"));
if (parser.Ok()) {
nameBody = parser.GetRemainderM();
result.Append(nameBody);
}
else {
result.Append(realName);
}
_.memory.Free(nameBody);
_.memory.Free(prefix);
_.memory.Free(parser);
return result.IntoText();
}
private function ArrayList GetAllGameModeMaps(GameMode gameMode) {
local int i, j;
local HashTable uniqueMapSet;
local ArrayList result;
local array<Text> usedMapLists;
local array<string> nextMapArray;
local Text nextMapName, lowerMapName;
uniqueMapSet = _.collections.EmptyHashTable(); // for testing map name uniqueness
result = _.collections.EmptyArrayList();
usedMapLists = gameMode.GetIncludedMapLists();
for (i = 0; i < usedMapLists.length; i += 1) {
nextMapArray = GetMapNameFromConfig(usedMapLists[i]);
for (j = 0; j < nextMapArray.length; j += 1) {
nextMapName = _.text.FromString(nextMapArray[j]);
// Use lower case version of map name for uniqueness testing to ignore characters' case
lowerMapName = nextMapName.LowerCopy();
if (!uniqueMapSet.HasKey(lowerMapName)) {
uniqueMapSet.SetItem(lowerMapName, none);
result.AddItem(nextMapName);
}
_.memory.Free(lowerMapName);
_.memory.Free(nextMapName);
}
}
_.memory.Free(uniqueMapSet);
_.memory.FreeMany(usedMapLists);
return result;
}
private function array<string> GetMapNameFromConfig(Text configName) {
local MapList mapConfig;
local array<string> result;
mapConfig = MapList(class'MapList'.static.GetConfigInstance(configName));
if (mapConfig == none) {
_.logger.Auto(warnMissingMapList).Arg(configName.Copy());
} else {
result = mapConfig.map;
_.memory.Free(mapConfig);
}
return result;
}
private function AppendMapsIntoVotingHandler(
array<VotingHandler.MapVoteMapList> newMapsPseudonim,
array<VotingHandler.MapVoteMapList> newMapsReal) {
local int i;
local XVotingHandler votingHandler;
votingHandler = GetVotingHandler();
if (votingHandler == none) {
_.logger.Auto(fatVotingHandlerMissing);
return;
}
for (i = 0; i < newMapsPseudonim.length; i += 1) {
pseudonimMapList[pseudonimMapList.length] = newMapsPseudonim[i];
}
for (i = 0; i < newMapsReal.length; i += 1) {
realMapList[realMapList.length] = newMapsReal[i];
}
}
private function bool HandleMapChange(
Actor sender,
out string message,
name type,
bool teamMessage
) {
local Parser parser;
local XVotingHandler votingHandler;
votingHandler = GetVotingHandler();
if (sender == none) return true;
if (votingHandler != sender) return true;
parser = _.text.ParseString(message);
parser.Match(P(ACEDIA_MAP_WON_COMMAND));
parser.Match(P("::"));
if (parser.Ok()) {
message = Repl(backupMessageMapWon, "%mapname%", parser.GetRemainderS());
} else {
parser.Match(P(ACEDIA_ADMIN_MAP_CHANGE_COMMAND));
parser.Match(P("::"));
if (parser.Ok()) {
message = Repl(backupMessageAdminMapChange, "%mapname%", parser.GetRemainderS());
}
}
if (parser.Ok()) {
votingHandler.mapList = realMapList;
votingHandler.mapCount = realMapList.length;
}
_.memory.Free(parser);
return true;
}
private function XVotingHandler GetVotingHandler() {
if (votingHandlerReference != none) {
return XVotingHandler(votingHandlerReference.Get());
}
return none;
}
defaultproperties {
ACEDIA_ADMIN_MAP_CHANGE_COMMAND = "ACEDIA_LAUNCHER:ADMIN_MAP_CHANGE:DEADBEEF"
ACEDIA_MAP_WON_COMMAND = "ACEDIA_LAUNCHER:MAP_WON:DEADBEEF"
fatVotingHandlerMissing = (l=LOG_Fatal,m="No voting `XVotingHandler` available. This is unexpected at this stage. Report this issue.")
warnMissingMapList = (l=LOG_Warning,m="Cannot find map list `%1`.")
}

4
sources/StartUp.uc

@ -1,7 +1,6 @@
/** /**
* This actor's role is to perform Acedia's server startup. * This actor's role is to perform Acedia's server startup.
* Copyright 2019-2023 Anton Tarasenko * Copyright 2019-2022 Anton Tarasenko
* 2023 Shtoyan
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -61,7 +60,6 @@ private function InitializeServer()
_ = class'Global'.static.GetInstance(); _ = class'Global'.static.GetInstance();
_server = class'ServerGlobal'.static.GetInstance(); _server = class'ServerGlobal'.static.GetInstance();
class'ServerLevelCore'.static.CreateLevelCore(self); class'ServerLevelCore'.static.CreateLevelCore(self);
class'MapList'.static.Initialize();
for (i = 0; i < class'Packages'.default.package.length; i += 1) { for (i = 0; i < class'Packages'.default.package.length; i += 1) {
_.environment.RegisterPackage_S(class'Packages'.default.package[i]); _.environment.RegisterPackage_S(class'Packages'.default.package[i]);
} }

47
sources/VotingHandlerAdapter.uc

@ -5,8 +5,7 @@
* data from Acedia's game modes. * data from Acedia's game modes.
* Requires `GameInfo`'s voting handler to be derived from * Requires `GameInfo`'s voting handler to be derived from
* `XVotingHandler`, which is satisfied by pretty much every used handler. * `XVotingHandler`, which is satisfied by pretty much every used handler.
* Copyright 2021-2023 Anton Tarasenko * Copyright 2021-2022 Anton Tarasenko
* 2023 Shtoyan
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -62,11 +61,9 @@ var private array<Text> availableGameModes;
// Finding voting handler is not cheap, so only do it once and then store it. // Finding voting handler is not cheap, so only do it once and then store it.
var private NativeActorRef votingHandlerReference; var private NativeActorRef votingHandlerReference;
// Save `VotingHandler`'s config to restore it before server travel - otherwise Acedia will alter // Save `VotingHandler`'s config to restore it before server travel -
// its config // otherwise Acedia will alter its config
var private array<VotingHandler.MapVoteGameConfig> backupVotingHandlerConfig; var private array<VotingHandler.MapVoteGameConfig> backupVotingHandlerConfig;
// Map list management logic
var private MapTool mapTool;
// Setting value of this flag to `true` indicates that map switching just // Setting value of this flag to `true` indicates that map switching just
// occurred and we need to recover some information from the previous map. // occurred and we need to recover some information from the previous map.
@ -82,7 +79,6 @@ var private config string targetGameMode;
// loaded. Store it in config variable for that. // loaded. Store it in config variable for that.
var private config int storedGameLength; var private config int storedGameLength;
// Aliases are an unnecessary overkill for difficulty names, so just define // Aliases are an unnecessary overkill for difficulty names, so just define
// them in special `string` arrays. // them in special `string` arrays.
// We accept not just these exact words, but any of their prefixes. // We accept not just these exact words, but any of their prefixes.
@ -91,17 +87,12 @@ var private const array<string> normalSynonyms;
var private const array<string> longSynonyms; var private const array<string> longSynonyms;
var private LoggerAPI.Definition fatNoXVotingHandler, fatBadGameConfigIndexVH; var private LoggerAPI.Definition fatNoXVotingHandler, fatBadGameConfigIndexVH;
var private LoggerAPI.Definition fatBadGameConfigIndexAdapter, warnMissingMapList; var private LoggerAPI.Definition fatBadGameConfigIndexAdapter;
protected function Constructor() {
mapTool = MapTool(_.memory.Allocate(class'MapTool'));
}
protected function Finalizer() { protected function Finalizer()
_.memory.Free(mapTool); {
_.memory.Free(votingHandlerReference); _.memory.Free(votingHandlerReference);
_.memory.FreeMany(availableGameModes); _.memory.FreeMany(availableGameModes);
mapTool = none;
votingHandlerReference = none; votingHandlerReference = none;
availableGameModes.length = 0; availableGameModes.length = 0;
} }
@ -114,30 +105,27 @@ protected function Finalizer() {
public final function InjectIntoVotingHandler() public final function InjectIntoVotingHandler()
{ {
local int i; local int i;
local string nextGameModePrefix;
local GameMode nextGameMode; local GameMode nextGameMode;
local XVotingHandler votingHandler; local XVotingHandler votingHandler;
local array<VotingHandler.MapVoteGameConfig> newVotingHandlerConfig; local array<VotingHandler.MapVoteGameConfig> newVotingHandlerConfig;
// `votingHandlerReference != none` means that we've already injected into voting handler
if (votingHandlerReference != none) { if (votingHandlerReference != none) {
return; return;
} }
votingHandler = XVotingHandler(_server.unreal.FindActorInstance( votingHandler = XVotingHandler(_server.unreal.FindActorInstance(
_server.unreal.GetGameType().votingHandlerClass)); _server.unreal.GetGameType().VotingHandlerClass));
if (votingHandler == none) { if (votingHandler == none)
{
_.logger.Auto(fatNoXVotingHandler); _.logger.Auto(fatNoXVotingHandler);
return; return;
} }
votingHandlerReference = _server.unreal.ActorRef(votingHandler); votingHandlerReference = _server.unreal.ActorRef(votingHandler);
// This cannot actuall fail at this point - we have valid `votingHandler` reference and
// `mapTool` is only initialized here (which can be executed only once)
mapTool.Initialize(votingHandlerReference);
availableGameModes = class'GameMode'.static.AvailableConfigs(); availableGameModes = class'GameMode'.static.AvailableConfigs();
for (i = 0; i < availableGameModes.length; i += 1) { for (i = 0; i < availableGameModes.length; i += 1)
nextGameMode = GameMode(class'GameMode'.static.GetConfigInstance(availableGameModes[i])); {
nextGameModePrefix = mapTool.AddGameMode(nextGameMode); nextGameMode = GameMode(class'GameMode'.static
newVotingHandlerConfig[i] = BuildVotingHandlerConfig(nextGameMode, nextGameModePrefix); .GetConfigInstance(availableGameModes[i]));
newVotingHandlerConfig[i] = BuildVotingHandlerConfig(nextGameMode);
// Setup proper game mode index // Setup proper game mode index
if (availableGameModes[i].ToString() == targetGameMode) { if (availableGameModes[i].ToString() == targetGameMode) {
votingHandler.currentGameConfig = i; votingHandler.currentGameConfig = i;
@ -145,22 +133,19 @@ public final function InjectIntoVotingHandler()
// Report omitted mutators / server options // Report omitted mutators / server options
nextGameMode.ReportBadMutatorNames(); nextGameMode.ReportBadMutatorNames();
nextGameMode.ReportBadOptions(); nextGameMode.ReportBadOptions();
_.memory.Free(nextGameMode);
} }
backupVotingHandlerConfig = votingHandler.gameConfig; backupVotingHandlerConfig = votingHandler.gameConfig;
votingHandler.gameConfig = newVotingHandlerConfig; votingHandler.gameConfig = newVotingHandlerConfig;
mapTool.Inject();
} }
private function VotingHandler.MapVoteGameConfig BuildVotingHandlerConfig( private function VotingHandler.MapVoteGameConfig BuildVotingHandlerConfig(
GameMode gameMode, GameMode gameMode)
string gameModePrefix)
{ {
local MutableText nextColoredName; local MutableText nextColoredName;
local VotingHandler.MapVoteGameConfig result; local VotingHandler.MapVoteGameConfig result;
result.gameClass = _.text.IntoString(gameMode.GetGameTypeClass()); result.gameClass = _.text.IntoString(gameMode.GetGameTypeClass());
result.prefix = gameModePrefix $ "-"; result.prefix = _.text.IntoString(gameMode.GetMapPrefix());
nextColoredName = gameMode nextColoredName = gameMode
.GetTitle() .GetTitle()
.IntoMutableText() .IntoMutableText()

Loading…
Cancel
Save