diff --git a/sources/BaseAPI/API/Commands/CommandList.uc b/sources/BaseAPI/API/Commands/CommandList.uc
new file mode 100644
index 0000000..bd2be60
--- /dev/null
+++ b/sources/BaseAPI/API/Commands/CommandList.uc
@@ -0,0 +1,249 @@
+/**
+ * Config class for storing map lists.
+ * 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 .
+ */
+class CommandList extends AcediaConfig
+ perObjectConfig
+ config(AcediaCommands);
+
+//! `CommandList` describes a set of commands and votings that can be made
+//! available to users inside Commands feature
+//!
+//! Optionally, permission configs can be specified for commands and votings,
+//! allowing server admins to create command lists for different groups player
+//! with the same commands, but different permissions.
+
+// For storing `class` - `string` pairs in the config
+struct CommandConfigStoragePair {
+ var public class cmd;
+ var public string config;
+};
+
+// For storing `class` - `string` pairs in the config
+struct VotingConfigStoragePair {
+ var public class vtn;
+ var public string config;
+};
+
+// For returning `class` - `Text` pairs into other Acedia classes
+struct EntityConfigPair {
+ var public class class;
+ var public Text config;
+};
+
+/// Allows to specify if this list should only be added when server is running
+/// in debug mode.
+/// `true` means yes, `false` means that list will always be available.
+var public config bool debugOnly;
+/// Adds a command of specified class with a "default" permissions config.
+var public config array< class > command;
+/// Adds a command of specified class with specified permissions config
+var public config array commandWith;
+/// Adds a voting of specified class with a "default" permissions config
+var public config array< class > voting;
+/// Adds a voting of specified class with specified permissions config
+var public config array votingWith;
+
+public final function array GetCommandData() {
+ local int i;
+ local EntityConfigPair nextPair;
+ local array result;
+
+ for (i = 0; i < command.length; i += 1) {
+ if (command[i] != none) {
+ nextPair.class = command[i];
+ result[result.length] = nextPair;
+ }
+ }
+ for (i = 0; i < commandWith.length; i += 1) {
+ if (commandWith[i].cmd != none) {
+ nextPair.class = commandWith[i].cmd;
+ if (commandWith[i].config != "") {
+ nextPair.config = _.text.FromString(commandWith[i].config);
+ }
+ result[result.length] = nextPair;
+ // Moved into the `result`
+ nextPair.config = none;
+ }
+ }
+ return result;
+}
+
+public final function array GetVotingData() {
+ local int i;
+ local EntityConfigPair nextPair;
+ local array result;
+
+ for (i = 0; i < voting.length; i += 1) {
+ if (voting[i] != none) {
+ nextPair.class = voting[i];
+ result[result.length] = nextPair;
+ }
+ }
+ for (i = 0; i < votingWith.length; i += 1) {
+ if (votingWith[i].vtn != none) {
+ nextPair.class = votingWith[i].vtn;
+ if (votingWith[i].config != "") {
+ nextPair.config = _.text.FromString(votingWith[i].config);
+ }
+ result[result.length] = nextPair;
+ // Moved into the `result`
+ nextPair.config = none;
+ }
+ }
+ return result;
+}
+
+protected function HashTable ToData() {
+ local int i;
+ local ArrayList entityArray;
+ local HashTable result, innerPair;
+
+ result = _.collections.EmptyHashTable();
+ result.SetBool(P("debugOnly"), debugOnly);
+ entityArray = _.collections.EmptyArrayList();
+ for (i = 0; i < command.length; i += 1) {
+ entityArray.AddString(string(command[i]));
+ }
+ result.SetItem(P("commands"), entityArray);
+ _.memory.Free(entityArray);
+
+ entityArray = _.collections.EmptyArrayList();
+ for (i = 0; i < voting.length; i += 1) {
+ entityArray.AddString(string(voting[i]));
+ }
+ result.SetItem(P("votings"), entityArray);
+ _.memory.Free(entityArray);
+
+ entityArray = _.collections.EmptyArrayList();
+ for (i = 0; i < commandWith.length; i += 1) {
+ innerPair = _.collections.EmptyHashTable();
+ innerPair.SetString(P("command"), string(commandWith[i].cmd));
+ innerPair.SetString(P("config"), commandWith[i].config);
+ entityArray.AddItem(innerPair);
+ _.memory.Free(innerPair);
+ }
+ result.SetItem(P("commandsWithConfig"), entityArray);
+ _.memory.Free(entityArray);
+
+ entityArray = _.collections.EmptyArrayList();
+ for (i = 0; i < votingWith.length; i += 1) {
+ innerPair = _.collections.EmptyHashTable();
+ innerPair.SetString(P("voting"), string(votingWith[i].vtn));
+ innerPair.SetString(P("config"), votingWith[i].config);
+ entityArray.AddItem(innerPair);
+ _.memory.Free(innerPair);
+ }
+ result.SetItem(P("votingsWithConfig"), entityArray);
+ _.memory.Free(entityArray);
+ return result;
+}
+
+protected function FromData(HashTable source) {
+ local int i;
+ local ArrayList entityArray;
+ local HashTable innerPair;
+ local class nextCommandClass;
+ local class nextVotingClass;
+ local CommandConfigStoragePair nextCommandPair;
+ local VotingConfigStoragePair nextVotingPair;
+
+ if (source == none) {
+ return;
+ }
+ debugOnly = source.GetBool(P("debugOnly"));
+ command.length = 0;
+ entityArray = source.GetArrayList(P("commands"));
+ if (entityArray != none) {
+ for (i = 0; i < entityArray.GetLength(); i += 1) {
+ nextCommandClass = class(_.memory.LoadClass_S(entityArray.GetString(i)));
+ if (nextCommandClass != none) {
+ command[command.length] = nextCommandClass;
+ }
+ }
+ }
+ _.memory.Free(entityArray);
+
+ voting.length = 0;
+ entityArray = source.GetArrayList(P("votings"));
+ if (entityArray != none) {
+ for (i = 0; i < entityArray.GetLength(); i += 1) {
+ nextVotingClass = class(_.memory.LoadClass_S(entityArray.GetString(i)));
+ if (nextVotingClass != none) {
+ voting[voting.length] = nextVotingClass;
+ }
+ }
+ }
+ _.memory.Free(entityArray);
+
+ commandWith.length = 0;
+ entityArray = source.GetArrayList(P("commandsWithConfig"));
+ if (entityArray != none) {
+ for (i = 0; i < entityArray.GetLength(); i += 1) {
+ innerPair = entityArray.GetHashTable(i);
+ if (innerPair == none) {
+ continue;
+ }
+ nextCommandPair.cmd =
+ class(_.memory.LoadClass_S(innerPair.GetString(P("command"))));
+ nextCommandPair.config = innerPair.GetString(P("config"));
+ _.memory.Free(innerPair);
+ if (nextCommandPair.cmd != none) {
+ commandWith[commandWith.length] = nextCommandPair;
+ }
+ }
+ }
+ _.memory.Free(entityArray);
+
+ votingWith.length = 0;
+ entityArray = source.GetArrayList(P("votingsWithConfig"));
+ if (entityArray != none) {
+ for (i = 0; i < entityArray.GetLength(); i += 1) {
+ innerPair = entityArray.GetHashTable(i);
+ if (innerPair == none) {
+ continue;
+ }
+ nextVotingPair.vtn =
+ class(_.memory.LoadClass_S(innerPair.GetString(P("voting"))));
+ nextVotingPair.config = innerPair.GetString(P("config"));
+ _.memory.Free(innerPair);
+ if (nextVotingPair.vtn != none) {
+ votingWith[votingWith.length] = nextVotingPair;
+ }
+ }
+ }
+ _.memory.Free(entityArray);
+}
+
+protected function DefaultIt() {
+ debugOnly = false;
+ command.length = 0;
+ commandWith.length = 0;
+ voting.length = 0;
+ votingWith.length = 0;
+ command[0] = class'ACommandHelp';
+ command[1] = class'ACommandVote';
+}
+
+defaultproperties {
+ configName = "AcediaCommands"
+ supportsDataConversion = true
+ debugOnly = false
+ command(0) = class'ACommandHelp'
+ command(1) = class'ACommandVote'
+}
\ No newline at end of file
diff --git a/sources/BaseAPI/API/Commands/CommandPermissions.uc b/sources/BaseAPI/API/Commands/CommandPermissions.uc
new file mode 100644
index 0000000..cfd13b8
--- /dev/null
+++ b/sources/BaseAPI/API/Commands/CommandPermissions.uc
@@ -0,0 +1,66 @@
+/**
+ * Author: dkanus
+ * Home repo: https://www.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 .
+ */
+class CommandPermissions extends AcediaConfig
+ perobjectconfig
+ config(AcediaCommands)
+ abstract;
+
+var public config array forbiddenSubCommands;
+
+protected function HashTable ToData() {
+ local int i;
+ local HashTable data;
+ local ArrayList forbiddenList;
+
+ data = _.collections.EmptyHashTable();
+ forbiddenList = _.collections.EmptyArrayList();
+ for (i = 0; i < forbiddenSubCommands.length; i += 1) {
+ forbiddenList.AddString(Locs(forbiddenSubCommands[i]));
+ }
+ data.SetItem(P("forbiddenSubCommands"), forbiddenList);
+ _.memory.Free(forbiddenList);
+ return data;
+}
+
+protected function FromData(HashTable source) {
+ local int i;
+ local ArrayList forbiddenList;
+
+ if (source == none) return;
+ forbiddenList = source.GetArrayList(P("forbiddenSubCommands"));
+ if (forbiddenList == none) return;
+
+ forbiddenSubCommands.length = 0;
+ for (i = 0; i < forbiddenList.GetLength(); i += 1) {
+ forbiddenSubCommands[i] = forbiddenList.GetString(i);
+ }
+ _.memory.Free(forbiddenList);
+}
+
+protected function DefaultIt() {
+ forbiddenSubCommands.length = 0;
+}
+
+defaultproperties {
+ configName = "AcediaCommands"
+ supportsDataConversion = true
+}
\ No newline at end of file
diff --git a/sources/BaseAPI/API/Commands/Voting/VotingPermissions.uc b/sources/BaseAPI/API/Commands/Voting/VotingPermissions.uc
new file mode 100644
index 0000000..7b5132c
--- /dev/null
+++ b/sources/BaseAPI/API/Commands/Voting/VotingPermissions.uc
@@ -0,0 +1,132 @@
+/**
+ * Author: dkanus
+ * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
+ * License: GPL
+ * Copyright 2021-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 .
+ */
+class VotingPermissions extends AcediaConfig
+ perobjectconfig
+ config(AcediaCommands);
+
+/// Determines the duration of the voting period, specified in seconds.
+/// Zero or negative values mean unlimited voting period.
+var public config float votingTime;
+
+/// Determines whether spectators are allowed to vote.
+var public config bool allowSpectatorVoting;
+/// Determines how draw will be interpreted.
+/// `true` means draw counts as a vote's success, `false` means draw counts as a vote's failure.
+var public config bool drawEqualsSuccess;
+/// Specifies which group(s) of players are allowed to see who makes what vote.
+var public config array allowedToVoteGroup;
+/// Specifies which group(s) of players are allowed to see who makes what vote.
+var public config array allowedToSeeVotesGroup;
+/// Specifies which group(s) of players are allowed to forcibly end voting.
+var public config array allowedToForceGroup;
+
+protected function HashTable ToData() {
+ local int i;
+ local HashTable data;
+ local ArrayList arrayOfTexts;
+
+ data = __().collections.EmptyHashTable();
+ data.SetFloat(P("votingTime"), votingTime);
+ data.SetBool(P("allowSpectatorVoting"), allowSpectatorVoting);
+ data.SetBool(P("drawEqualsSuccess"), drawEqualsSuccess);
+
+ arrayOfTexts = _.collections.EmptyArrayList();
+ for (i = 0; i < allowedToVoteGroup.length; i += 1) {
+ arrayOfTexts.AddString(allowedToVoteGroup[i]);
+ }
+ data.SetItem(P("allowedToVoteGroup"), arrayOfTexts);
+ _.memory.Free(arrayOfTexts);
+
+ arrayOfTexts = _.collections.EmptyArrayList();
+ for (i = 0; i < allowedToSeeVotesGroup.length; i += 1) {
+ arrayOfTexts.AddString(allowedToSeeVotesGroup[i]);
+ }
+ data.SetItem(P("allowedToSeeVotesGroup"), arrayOfTexts);
+ _.memory.Free(arrayOfTexts);
+
+ arrayOfTexts = _.collections.EmptyArrayList();
+ for (i = 0; i < allowedToForceGroup.length; i += 1) {
+ arrayOfTexts.AddString(allowedToForceGroup[i]);
+ }
+ data.SetItem(P("allowedToForceGroup"), arrayOfTexts);
+ _.memory.Free(arrayOfTexts);
+ return data;
+}
+
+protected function FromData(HashTable source) {
+ local int i;
+ local ArrayList arrayOfTexts;
+
+ if (source == none) {
+ return;
+ }
+ votingTime = source.GetFloat(P("votingTime"), 30.0);
+ allowSpectatorVoting = source.GetBool(P("allowSpectatorVoting"), false);
+ drawEqualsSuccess = source.GetBool(P("drawEqualsSuccess"), false);
+
+ allowedToVoteGroup.length = 0;
+ arrayOfTexts = source.GetArrayList(P("allowedToVoteGroup"));
+ for (i = 0; i < arrayOfTexts.GetLength(); i += 1) {
+ allowedToVoteGroup[allowedToVoteGroup.length] = arrayOfTexts.GetString(i);
+ }
+
+ allowedToSeeVotesGroup.length = 0;
+ arrayOfTexts = source.GetArrayList(P("allowedToSeeVotesGroup"));
+ for (i = 0; i < arrayOfTexts.GetLength(); i += 1) {
+ allowedToSeeVotesGroup[allowedToSeeVotesGroup.length] = arrayOfTexts.GetString(i);
+ }
+ _.memory.Free(arrayOfTexts);
+
+ allowedToForceGroup.length = 0;
+ arrayOfTexts = source.GetArrayList(P("allowedToForceGroup"));
+ for (i = 0; i < arrayOfTexts.GetLength(); i += 1) {
+ allowedToForceGroup[allowedToForceGroup.length] = arrayOfTexts.GetString(i);
+ }
+ _.memory.Free(arrayOfTexts);
+}
+
+protected function DefaultIt() {
+ votingTime = 30.0;
+ drawEqualsSuccess = false;
+ allowSpectatorVoting = false;
+
+ allowedToVoteGroup.length = 0;
+ allowedToSeeVotesGroup.length = 0;
+ allowedToForceGroup.length = 0;
+
+ allowedToVoteGroup[0] = "all";
+ allowedToSeeVotesGroup[0] = "all";
+ allowedToForceGroup[0] = "admin";
+ allowedToForceGroup[1] = "moderator";
+}
+
+defaultproperties {
+ configName = "AcediaCommands"
+ supportsDataConversion = true
+ votingTime = 30.0
+ drawEqualsSuccess = false
+ allowSpectatorVoting = false
+ allowedToVoteGroup(0) = "all"
+ allowedToSeeVotesGroup(0) = "all"
+ allowedToForceGroup(0) = "admin"
+ allowedToForceGroup(1) = "moderator"
+}
\ No newline at end of file
diff --git a/sources/BaseAPI/API/Commands/Voting/VotingSettings.uc b/sources/BaseAPI/API/Commands/Voting/VotingSettings.uc
deleted file mode 100644
index 6467c52..0000000
--- a/sources/BaseAPI/API/Commands/Voting/VotingSettings.uc
+++ /dev/null
@@ -1,75 +0,0 @@
-class VotingSettings extends FeatureConfig
- perobjectconfig
- config(AcediaVoting);
-
-/// Determines the duration of the voting period, specified in seconds.
-var public config float votingTime;
-
-/// Determines whether spectators are allowed to vote.
-var public config bool allowSpectatorVoting;
-/// Specifies which group(s) of players are allowed to see who makes what vote.
-var public config array allowedToSeeVotesGroup;
-/// Specifies which group(s) of players are allowed to vote.
-var public config array allowedToVoteGroup;
-
-protected function HashTable ToData() {
- local int i;
- local HashTable data;
- local ArrayList arrayOfTexts;
-
- data = __().collections.EmptyHashTable();
- data.SetFloat(P("votingTime"), votingTime);
- data.SetBool(P("allowSpectatorVoting"), allowSpectatorVoting);
-
- arrayOfTexts = _.collections.EmptyArrayList();
- for (i = 0; i < allowedToSeeVotesGroup.length; i += 1) {
- arrayOfTexts.AddString(allowedToSeeVotesGroup[i]);
- }
- data.SetItem(P("allowedToSeeVotesGroup"), arrayOfTexts);
- _.memory.Free(arrayOfTexts);
-
- arrayOfTexts = _.collections.EmptyArrayList();
- for (i = 0; i < allowedToVoteGroup.length; i += 1) {
- arrayOfTexts.AddString(allowedToVoteGroup[i]);
- }
- data.SetItem(P("allowedToVoteGroup"), arrayOfTexts);
- _.memory.Free(arrayOfTexts);
- return data;
-}
-
-protected function FromData(HashTable source) {
- local int i;
- local ArrayList arrayOfTexts;
-
- if (source == none) {
- return;
- }
- votingTime = source.GetFloat(P("votingTime"), 30.0);
- allowSpectatorVoting = source.GetBool(P("allowSpectatorVoting"), false);
-
- allowedToSeeVotesGroup.length = 0;
- arrayOfTexts = source.GetArrayList(P("allowedToSeeVotesGroup"));
- for (i = 0; i < arrayOfTexts.GetLength(); i += 1) {
- allowedToSeeVotesGroup[allowedToSeeVotesGroup.length] = arrayOfTexts.GetString(i);
- }
- _.memory.Free(arrayOfTexts);
-
- allowedToVoteGroup.length = 0;
- arrayOfTexts = source.GetArrayList(P("allowedToVoteGroup"));
- for (i = 0; i < arrayOfTexts.GetLength(); i += 1) {
- allowedToVoteGroup[allowedToVoteGroup.length] = arrayOfTexts.GetString(i);
- }
- _.memory.Free(arrayOfTexts);
-}
-
-protected function DefaultIt() {
- votingTime = 30.0;
- allowSpectatorVoting = false;
- allowedToSeeVotesGroup.length = 0;
- allowedToVoteGroup.length = 0;
- allowedToVoteGroup[0] = "everybody";
-}
-
-defaultproperties {
- configName = "AcediaVoting"
-}
\ No newline at end of file