Compare commits
No commits in common. 'e8ae6fd8d16dc51d22ad4f5525c5399a447e9f69' and '70c41a5926fe78978cf6ca9236cb2ee4ada7b43f' have entirely different histories.
e8ae6fd8d1
...
70c41a5926
47 changed files with 2595 additions and 6417 deletions
@ -1,8 +1,8 @@ |
|||||||
; This config file allows you to configure command aliases. |
; This config file allows you to configure command aliases. |
||||||
; Remember that aliases are case-insensitive. |
; Remember that aliases are case-insensitive. |
||||||
[AcediaCore.CommandAliasSource] |
[AcediaCore.CommandAliasSource] |
||||||
record=(alias="yes",value="vote.yes") |
record=(alias="yes",value="vote yes") |
||||||
record=(alias="no",value="vote.no") |
record=(alias="no",value="vote no") |
||||||
|
|
||||||
[help CommandAliases] |
[help CommandAliases] |
||||||
Alias="hlp" |
Alias="hlp" |
@ -1,117 +0,0 @@ |
|||||||
[default Commands] |
|
||||||
autoEnable=true |
|
||||||
;= Setting this to `true` enables players to input commands with "mutate" |
|
||||||
;= console command. |
|
||||||
;= Default is `true`. |
|
||||||
useMutateInput=true |
|
||||||
;= Setting this to `true` enables players to input commands right in the chat |
|
||||||
;= by prepending them with [`chatCommandPrefix`]. |
|
||||||
;= Default is `true`. |
|
||||||
useChatInput=true |
|
||||||
;= Chat messages, prepended by this prefix will be treated as commands. |
|
||||||
;= Default is "!". Empty values are also treated as "!". |
|
||||||
chatCommandPrefix=! |
|
||||||
;= Allows to specify which user groups are used in determining command/votings |
|
||||||
;= permission. |
|
||||||
;= They must be specified in the order of importance: from the group with |
|
||||||
;= highest level of permissions to the lowest. When determining player's |
|
||||||
;= permission to use a certain command/voting, his group with the highest |
|
||||||
;= available permissions will be used. |
|
||||||
commandGroup=admin |
|
||||||
commandGroup=moderator |
|
||||||
commandGroup=trusted |
|
||||||
commandGroup=all |
|
||||||
;= Add a specified `CommandList` to the specified user group |
|
||||||
addCommandList=(name="default",for="all") |
|
||||||
addCommandList=(name="moderator",for="moderator") |
|
||||||
addCommandList=(name="admin",for="admin") |
|
||||||
addCommandList=(name="debug",for="admin") |
|
||||||
;= Allows to specify a name for a certain command class |
|
||||||
;= |
|
||||||
;= NOTE:By default command choses that name by itself and its not recommended |
|
||||||
;= to override it. You should only use this setting in case there is naming |
|
||||||
;= conflict between commands from different packages. |
|
||||||
;=renamingRule=(rename=class'ACommandHelp',to="lol") |
|
||||||
|
|
||||||
;= Allows to specify a name for a certain voting class |
|
||||||
;= |
|
||||||
;= NOTE:By default voting choses that name by itself and its not recommended |
|
||||||
;= to override it. You should only use this setting in case there is naming |
|
||||||
;= conflict between votings from different packages. |
|
||||||
;=votingRenamingRule=(rename=class'Voting',to="lol") |
|
||||||
|
|
||||||
;= `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. |
|
||||||
[default CommandList] |
|
||||||
;= 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. |
|
||||||
debugOnly=false |
|
||||||
;= Adds a command of specified class with a "default" permissions config |
|
||||||
command=class'ACommandHelp' |
|
||||||
command=class'ACommandVote' |
|
||||||
;= Adds a voting of specified class with a "default" permissions config |
|
||||||
voting=class'Voting' |
|
||||||
;= Adds a command of specified class with specified permissions config |
|
||||||
;=commandWith=(cmd=,config="") |
|
||||||
;= Adds a voting of specified class with specified permissions config |
|
||||||
;=commandWith=(vtn=,config="") |
|
||||||
|
|
||||||
[debug CommandList] |
|
||||||
debugOnly=true |
|
||||||
command=class'ACommandFakers' |
|
||||||
|
|
||||||
[moderator CommandList] |
|
||||||
command=class'ACommandNotify' |
|
||||||
|
|
||||||
[admin CommandList] |
|
||||||
command=class'ACommandSideEffects' |
|
||||||
|
|
||||||
;= `VotingPermissions` describe use permission settings for a voting |
|
||||||
[default VotingPermissions] |
|
||||||
;= Determines the duration of the voting period, specified in seconds. |
|
||||||
;= Zero or negative values mean unlimited voting period. |
|
||||||
votingTime=30 |
|
||||||
;= Determines how draw will be interpreted. |
|
||||||
;= `true` means draw counts as a vote's success, `false` means draw counts as a vote's failure. |
|
||||||
drawEqualsSuccess=false |
|
||||||
;= Determines whether spectators are allowed to vote. |
|
||||||
allowSpectatorVoting=false |
|
||||||
;= Specifies which group(s) of players are allowed to see who makes what vote. |
|
||||||
allowedToVoteGroup=all |
|
||||||
;= Specifies which group(s) of players are allowed to see who makes what vote. |
|
||||||
allowedToSeeVotesGroup=all |
|
||||||
;= Specifies which group(s) of players are allowed to forcibly end voting. |
|
||||||
allowedToForceGroup=admin |
|
||||||
allowedToForceGroup=moderator |
|
||||||
|
|
||||||
[anonymous VotingPermissions] |
|
||||||
votingTime=30 |
|
||||||
drawEqualsSuccess=false |
|
||||||
allowSpectatorVoting=false |
|
||||||
allowedToVoteGroup=all |
|
||||||
allowedToSeeVotesGroup=admin |
|
||||||
allowedToSeeVotesGroup=moderator |
|
||||||
allowedToForceGroup=admin |
|
||||||
allowedToForceGroup=moderator |
|
||||||
|
|
||||||
[moderator VotingPermissions] |
|
||||||
votingTime=60 |
|
||||||
drawEqualsSuccess=false |
|
||||||
allowSpectatorVoting=false |
|
||||||
allowedToVoteGroup=admin |
|
||||||
allowedToVoteGroup=moderator |
|
||||||
allowedToSeeVotesGroup=admin |
|
||||||
allowedToForceGroup=admin |
|
||||||
|
|
||||||
[admin VotingPermissions] |
|
||||||
votingTime=60 |
|
||||||
drawEqualsSuccess=false |
|
||||||
allowSpectatorVoting=true |
|
||||||
allowedToVoteGroup=admin |
|
||||||
allowedToSeeVotesGroup=admin |
|
||||||
allowedToForceGroup=admin |
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,249 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
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<Command>` - `string` pairs in the config |
|
||||||
struct CommandConfigStoragePair { |
|
||||||
var public class<Command> cmd; |
|
||||||
var public string config; |
|
||||||
}; |
|
||||||
|
|
||||||
// For storing `class` - `string` pairs in the config |
|
||||||
struct VotingConfigStoragePair { |
|
||||||
var public class<Voting> vtn; |
|
||||||
var public string config; |
|
||||||
}; |
|
||||||
|
|
||||||
// For returning `class` - `Text` pairs into other Acedia classes |
|
||||||
struct EntityConfigPair { |
|
||||||
var public class<AcediaObject> 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> > command; |
|
||||||
/// Adds a command of specified class with specified permissions config |
|
||||||
var public config array<CommandConfigStoragePair> commandWith; |
|
||||||
/// Adds a voting of specified class with a "default" permissions config |
|
||||||
var public config array< class<Voting> > voting; |
|
||||||
/// Adds a voting of specified class with specified permissions config |
|
||||||
var public config array<VotingConfigStoragePair> votingWith; |
|
||||||
|
|
||||||
public final function array<EntityConfigPair> GetCommandData() { |
|
||||||
local int i; |
|
||||||
local EntityConfigPair nextPair; |
|
||||||
local array<EntityConfigPair> 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<EntityConfigPair> GetVotingData() { |
|
||||||
local int i; |
|
||||||
local EntityConfigPair nextPair; |
|
||||||
local array<EntityConfigPair> 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<Command> nextCommandClass; |
|
||||||
local class<Voting> 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<Command>(_.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<Voting>(_.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<Command>(_.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<Voting>(_.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' |
|
||||||
} |
|
@ -1,66 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CommandPermissions extends AcediaConfig |
|
||||||
perobjectconfig |
|
||||||
config(AcediaCommands) |
|
||||||
abstract; |
|
||||||
|
|
||||||
var public config array<string> 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 |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CommandsAPI_OnCommandAdded_Signal extends Signal; |
|
||||||
|
|
||||||
public final function bool Emit(class<Command> addedClass, Text usedName) { |
|
||||||
local Slot nextSlot; |
|
||||||
|
|
||||||
StartIterating(); |
|
||||||
nextSlot = GetNextSlot(); |
|
||||||
while (nextSlot != none) { |
|
||||||
CommandsAPI_OnCommandAdded_Slot(nextSlot).connect(addedClass, usedName); |
|
||||||
nextSlot = GetNextSlot(); |
|
||||||
} |
|
||||||
CleanEmptySlots(); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
relatedSlotClass = class'CommandsAPI_OnCommandAdded_Slot' |
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CommandsAPI_OnCommandAdded_Slot extends Slot; |
|
||||||
|
|
||||||
delegate connect(class<Command> addedClass, Text usedName) { |
|
||||||
DummyCall(); |
|
||||||
} |
|
||||||
|
|
||||||
protected function Constructor() { |
|
||||||
connect = none; |
|
||||||
} |
|
||||||
|
|
||||||
protected function Finalizer() { |
|
||||||
super.Finalizer(); |
|
||||||
connect = none; |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CommandsAPI_OnCommandRemoved_Signal extends Signal; |
|
||||||
|
|
||||||
public final function bool Emit(class<Command> removedClass) { |
|
||||||
local Slot nextSlot; |
|
||||||
|
|
||||||
StartIterating(); |
|
||||||
nextSlot = GetNextSlot(); |
|
||||||
while (nextSlot != none) { |
|
||||||
CommandsAPI_OnCommandRemoved_Slot(nextSlot).connect(removedClass); |
|
||||||
nextSlot = GetNextSlot(); |
|
||||||
} |
|
||||||
CleanEmptySlots(); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
relatedSlotClass = class'CommandsAPI_OnCommandRemoved_Slot' |
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CommandsAPI_OnCommandRemoved_Slot extends Slot; |
|
||||||
|
|
||||||
delegate connect(class<Command> removedClass) { |
|
||||||
DummyCall(); |
|
||||||
} |
|
||||||
|
|
||||||
protected function Constructor() { |
|
||||||
connect = none; |
|
||||||
} |
|
||||||
|
|
||||||
protected function Finalizer() { |
|
||||||
super.Finalizer(); |
|
||||||
connect = none; |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CommandsAPI_OnVotingAdded_Signal extends Signal; |
|
||||||
|
|
||||||
public final function bool Emit(class<Voting> addedClass, Text usedName) { |
|
||||||
local Slot nextSlot; |
|
||||||
|
|
||||||
StartIterating(); |
|
||||||
nextSlot = GetNextSlot(); |
|
||||||
while (nextSlot != none) { |
|
||||||
CommandsAPI_OnVotingAdded_Slot(nextSlot).connect(addedClass, usedName); |
|
||||||
nextSlot = GetNextSlot(); |
|
||||||
} |
|
||||||
CleanEmptySlots(); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
relatedSlotClass = class'CommandsAPI_OnVotingAdded_Slot' |
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CommandsAPI_OnVotingAdded_Slot extends Slot; |
|
||||||
|
|
||||||
delegate connect(class<Voting> addedClass, Text usedName) { |
|
||||||
DummyCall(); |
|
||||||
} |
|
||||||
|
|
||||||
protected function Constructor() { |
|
||||||
connect = none; |
|
||||||
} |
|
||||||
|
|
||||||
protected function Finalizer() { |
|
||||||
super.Finalizer(); |
|
||||||
connect = none; |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CommandsAPI_OnVotingEnded_Signal extends Signal; |
|
||||||
|
|
||||||
public final function bool Emit(bool success, HashTable arguments) { |
|
||||||
local Slot nextSlot; |
|
||||||
|
|
||||||
StartIterating(); |
|
||||||
nextSlot = GetNextSlot(); |
|
||||||
while (nextSlot != none) { |
|
||||||
CommandsAPI_OnVotingEnded_Slot(nextSlot).connect(success, arguments); |
|
||||||
nextSlot = GetNextSlot(); |
|
||||||
} |
|
||||||
CleanEmptySlots(); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
relatedSlotClass = class'CommandsAPI_OnVotingEnded_Slot' |
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CommandsAPI_OnVotingEnded_Slot extends Slot; |
|
||||||
|
|
||||||
delegate connect(bool success, HashTable arguments) { |
|
||||||
DummyCall(); |
|
||||||
} |
|
||||||
|
|
||||||
protected function Constructor() { |
|
||||||
connect = none; |
|
||||||
} |
|
||||||
|
|
||||||
protected function Finalizer() { |
|
||||||
super.Finalizer(); |
|
||||||
connect = none; |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CommandsAPI_OnVotingRemoved_Signal extends Signal; |
|
||||||
|
|
||||||
public final function bool Emit(class<Voting> removedClass) { |
|
||||||
local Slot nextSlot; |
|
||||||
|
|
||||||
StartIterating(); |
|
||||||
nextSlot = GetNextSlot(); |
|
||||||
while (nextSlot != none) { |
|
||||||
CommandsAPI_OnVotingRemoved_Slot(nextSlot).connect(removedClass); |
|
||||||
nextSlot = GetNextSlot(); |
|
||||||
} |
|
||||||
CleanEmptySlots(); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
relatedSlotClass = class'CommandsAPI_OnVotingRemoved_Slot' |
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CommandsAPI_OnVotingRemoved_Slot extends Slot; |
|
||||||
|
|
||||||
delegate connect(class<Voting> removedClass) { |
|
||||||
DummyCall(); |
|
||||||
} |
|
||||||
|
|
||||||
protected function Constructor() { |
|
||||||
connect = none; |
|
||||||
} |
|
||||||
|
|
||||||
protected function Finalizer() { |
|
||||||
super.Finalizer(); |
|
||||||
connect = none; |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
} |
|
@ -1,351 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class TEST_Voting extends TestCase |
|
||||||
abstract |
|
||||||
dependsOn(VotingModel); |
|
||||||
|
|
||||||
enum ExpectedOutcome { |
|
||||||
TEST_EO_Continue, |
|
||||||
TEST_EO_End, |
|
||||||
}; |
|
||||||
|
|
||||||
protected static function VotingModel MakeVotingModel(bool drawMeansWin) { |
|
||||||
local VotingModel model; |
|
||||||
|
|
||||||
model = VotingModel(__().memory.Allocate(class'VotingModel')); |
|
||||||
model.Start(drawMeansWin); |
|
||||||
return model; |
|
||||||
} |
|
||||||
|
|
||||||
protected static function SetVoters( |
|
||||||
VotingModel model, |
|
||||||
optional string voterID0, |
|
||||||
optional string voterID1, |
|
||||||
optional string voterID2, |
|
||||||
optional string voterID3, |
|
||||||
optional string voterID4, |
|
||||||
optional string voterID5, |
|
||||||
optional string voterID6, |
|
||||||
optional string voterID7, |
|
||||||
optional string voterID8, |
|
||||||
optional string voterID9 |
|
||||||
) { |
|
||||||
local UserID nextID; |
|
||||||
local array<UserID> voterIDs; |
|
||||||
|
|
||||||
if (voterID0 != "") { |
|
||||||
nextID = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
nextID.Initialize(__().text.FromString(voterID0)); |
|
||||||
voterIDs[voterIDs.length] = nextID; |
|
||||||
} |
|
||||||
if (voterID1 != "") { |
|
||||||
nextID = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
nextID.Initialize(__().text.FromString(voterID1)); |
|
||||||
voterIDs[voterIDs.length] = nextID; |
|
||||||
} |
|
||||||
if (voterID2 != "") { |
|
||||||
nextID = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
nextID.Initialize(__().text.FromString(voterID2)); |
|
||||||
voterIDs[voterIDs.length] = nextID; |
|
||||||
} |
|
||||||
if (voterID3 != "") { |
|
||||||
nextID = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
nextID.Initialize(__().text.FromString(voterID3)); |
|
||||||
voterIDs[voterIDs.length] = nextID; |
|
||||||
} |
|
||||||
if (voterID4 != "") { |
|
||||||
nextID = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
nextID.Initialize(__().text.FromString(voterID4)); |
|
||||||
voterIDs[voterIDs.length] = nextID; |
|
||||||
} |
|
||||||
if (voterID5 != "") { |
|
||||||
nextID = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
nextID.Initialize(__().text.FromString(voterID5)); |
|
||||||
voterIDs[voterIDs.length] = nextID; |
|
||||||
} |
|
||||||
if (voterID6 != "") { |
|
||||||
nextID = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
nextID.Initialize(__().text.FromString(voterID6)); |
|
||||||
voterIDs[voterIDs.length] = nextID; |
|
||||||
} |
|
||||||
if (voterID7 != "") { |
|
||||||
nextID = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
nextID.Initialize(__().text.FromString(voterID7)); |
|
||||||
voterIDs[voterIDs.length] = nextID; |
|
||||||
} |
|
||||||
if (voterID8 != "") { |
|
||||||
nextID = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
nextID.Initialize(__().text.FromString(voterID8)); |
|
||||||
voterIDs[voterIDs.length] = nextID; |
|
||||||
} |
|
||||||
if (voterID9 != "") { |
|
||||||
nextID = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
nextID.Initialize(__().text.FromString(voterID9)); |
|
||||||
voterIDs[voterIDs.length] = nextID; |
|
||||||
} |
|
||||||
model.UpdatePotentialVoters(voterIDs); |
|
||||||
} |
|
||||||
|
|
||||||
protected static function MakeFaultyYesVote( |
|
||||||
VotingModel model, |
|
||||||
string voterID, |
|
||||||
VotingModel.VotingResult expected) { |
|
||||||
local UserID id; |
|
||||||
|
|
||||||
id = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
id.Initialize(__().text.FromString(voterID)); |
|
||||||
Issue("Illegal vote had unexpected result."); |
|
||||||
TEST_ExpectTrue(model.CastVote(id, true) == expected); |
|
||||||
} |
|
||||||
|
|
||||||
protected static function MakeFaultyNoVote( |
|
||||||
VotingModel model, |
|
||||||
string voterID, |
|
||||||
VotingModel.VotingResult expected) { |
|
||||||
local UserID id; |
|
||||||
|
|
||||||
id = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
id.Initialize(__().text.FromString(voterID)); |
|
||||||
Issue("Illegal vote had unexpected result."); |
|
||||||
TEST_ExpectTrue(model.CastVote(id, false) == expected); |
|
||||||
} |
|
||||||
|
|
||||||
protected static function VoteYes(VotingModel model, string voterID, ExpectedOutcome expected) { |
|
||||||
local UserID id; |
|
||||||
|
|
||||||
id = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
id.Initialize(__().text.FromString(voterID)); |
|
||||||
Issue("Failed to add legitimate vote."); |
|
||||||
TEST_ExpectTrue(model.CastVote(id, true) == VFR_Success); |
|
||||||
if (expected == TEST_EO_Continue) { |
|
||||||
Issue("Vote, that shouldn't have ended voting, ended it."); |
|
||||||
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
|
||||||
} else if (expected == TEST_EO_End) { |
|
||||||
Issue("Vote, that should've ended voting with one side's victory, didn't do it."); |
|
||||||
TEST_ExpectTrue(model.GetStatus() == VPM_Success); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected static function VoteNo(VotingModel model, string voterID, ExpectedOutcome expected) { |
|
||||||
local UserID id; |
|
||||||
|
|
||||||
id = UserID(__().memory.Allocate(class'UserID')); |
|
||||||
id.Initialize(__().text.FromString(voterID)); |
|
||||||
Issue("Failed to add legitimate vote."); |
|
||||||
TEST_ExpectTrue(model.CastVote(id, false) == VFR_Success); |
|
||||||
if (expected == TEST_EO_Continue) { |
|
||||||
Issue("Vote, that shouldn't have ended voting, ended it."); |
|
||||||
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
|
||||||
} else if (expected == TEST_EO_End) { |
|
||||||
Issue("Vote, that should've ended voting with one side's victory, didn't do it."); |
|
||||||
TEST_ExpectTrue(model.GetStatus() == VPM_Failure); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected static function TESTS() { |
|
||||||
Test_VotingModel(); |
|
||||||
} |
|
||||||
|
|
||||||
protected static function Test_VotingModel() { |
|
||||||
SubTest_YesVoting(); |
|
||||||
SubTest_NoVoting(); |
|
||||||
SubTest_FaultyVoting(); |
|
||||||
SubTest_DisconnectVoting_DrawMeansWin(); |
|
||||||
SubTest_DisconnectVoting_DrawMeansLoss(); |
|
||||||
SubTest_ReconnectVoting_DrawMeansWin(); |
|
||||||
SubTest_ReconnectVoting_DrawMeansLoss(); |
|
||||||
} |
|
||||||
|
|
||||||
protected static function SubTest_YesVoting() { |
|
||||||
local VotingModel model; |
|
||||||
|
|
||||||
Context("Testing \"yes\" voting."); |
|
||||||
model = MakeVotingModel(true); |
|
||||||
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
|
||||||
VoteYes(model, "2", TEST_EO_Continue); |
|
||||||
VoteYes(model, "4", TEST_EO_Continue); |
|
||||||
VoteNo(model, "6", TEST_EO_Continue); |
|
||||||
VoteNo(model, "1", TEST_EO_Continue); |
|
||||||
VoteYes(model, "6", TEST_EO_End); |
|
||||||
|
|
||||||
model = MakeVotingModel(false); |
|
||||||
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
|
||||||
VoteYes(model, "2", TEST_EO_Continue); |
|
||||||
VoteYes(model, "4", TEST_EO_Continue); |
|
||||||
VoteYes(model, "3", TEST_EO_Continue); |
|
||||||
VoteNo(model, "6", TEST_EO_Continue); |
|
||||||
VoteNo(model, "1", TEST_EO_Continue); |
|
||||||
VoteYes(model, "6", TEST_EO_End); |
|
||||||
} |
|
||||||
|
|
||||||
protected static function SubTest_NoVoting() { |
|
||||||
local VotingModel model; |
|
||||||
|
|
||||||
Context("Testing \"no\" voting."); |
|
||||||
model = MakeVotingModel(true); |
|
||||||
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
|
||||||
VoteNo(model, "1", TEST_EO_Continue); |
|
||||||
VoteNo(model, "2", TEST_EO_Continue); |
|
||||||
VoteNo(model, "6", TEST_EO_Continue); |
|
||||||
VoteYes(model, "5", TEST_EO_Continue); |
|
||||||
VoteYes(model, "4", TEST_EO_Continue); |
|
||||||
VoteNo(model, "5", TEST_EO_End); |
|
||||||
|
|
||||||
model = MakeVotingModel(false); |
|
||||||
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
|
||||||
VoteNo(model, "1", TEST_EO_Continue); |
|
||||||
VoteNo(model, "2", TEST_EO_Continue); |
|
||||||
VoteYes(model, "5", TEST_EO_Continue); |
|
||||||
VoteYes(model, "4", TEST_EO_Continue); |
|
||||||
VoteNo(model, "5", TEST_EO_End); |
|
||||||
} |
|
||||||
|
|
||||||
protected static function SubTest_FaultyVoting() { |
|
||||||
local VotingModel model; |
|
||||||
|
|
||||||
Context("Testing \"faulty\" voting."); |
|
||||||
model = MakeVotingModel(false); |
|
||||||
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
|
||||||
VoteYes(model, "3", TEST_EO_Continue); |
|
||||||
VoteYes(model, "5", TEST_EO_Continue); |
|
||||||
VoteYes(model, "2", TEST_EO_Continue); |
|
||||||
VoteNo(model, "5", TEST_EO_Continue); |
|
||||||
VoteYes(model, "1", TEST_EO_Continue); |
|
||||||
VoteYes(model, "6", TEST_EO_End); |
|
||||||
MakeFaultyYesVote(model, "4", VFR_VotingEnded); // voting already ended, so no more votes here |
|
||||||
|
|
||||||
model = MakeVotingModel(true); |
|
||||||
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
|
||||||
VoteYes(model, "3", TEST_EO_Continue); |
|
||||||
VoteYes(model, "5", TEST_EO_Continue); |
|
||||||
VoteNo(model, "5", TEST_EO_Continue); |
|
||||||
VoteYes(model, "1", TEST_EO_Continue); |
|
||||||
VoteYes(model, "6", TEST_EO_End); |
|
||||||
MakeFaultyYesVote(model, "4", VFR_VotingEnded); // voting already ended, so no more votes here |
|
||||||
} |
|
||||||
|
|
||||||
protected static function SubTest_DisconnectVoting_DrawMeansWin() { |
|
||||||
local VotingModel model; |
|
||||||
|
|
||||||
Context("Testing \"disconnect\" voting when draw means victory."); |
|
||||||
model = MakeVotingModel(true); |
|
||||||
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
|
||||||
VoteYes(model, "1", TEST_EO_Continue); |
|
||||||
VoteYes(model, "2", TEST_EO_Continue); |
|
||||||
VoteYes(model, "3", TEST_EO_Continue); |
|
||||||
VoteNo(model, "5", TEST_EO_Continue); |
|
||||||
VoteYes(model, "5", TEST_EO_Continue); |
|
||||||
VoteNo(model, "5", TEST_EO_Continue); |
|
||||||
VoteYes(model, "4", TEST_EO_Continue); |
|
||||||
VoteNo(model, "6", TEST_EO_Continue); |
|
||||||
SetVoters(model, "2", "4", "5", "6", "8", "9", "10"); // remove 1, 3, 7 - 2 "yes" votes |
|
||||||
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
|
||||||
VoteNo(model, "8", TEST_EO_Continue); |
|
||||||
VoteYes(model, "5", TEST_EO_Continue); |
|
||||||
VoteNo(model, "9", TEST_EO_Continue); |
|
||||||
// Here we're at 3 "no" votes, 3 "yes" votes out of 7 total; |
|
||||||
// disconnect "6" and "9" for "yes" to win |
|
||||||
SetVoters(model, "2", "4", "5", "8", "10"); |
|
||||||
Issue("Unexpected result after voting users disconnected."); |
|
||||||
TEST_ExpectTrue(model.GetStatus() == VPM_Success); |
|
||||||
} |
|
||||||
|
|
||||||
protected static function SubTest_DisconnectVoting_DrawMeansLoss() { |
|
||||||
local VotingModel model; |
|
||||||
|
|
||||||
Context("Testing \"disconnect\" voting when draw means loss."); |
|
||||||
model = MakeVotingModel(false); |
|
||||||
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
|
||||||
VoteYes(model, "1", TEST_EO_Continue); |
|
||||||
VoteYes(model, "2", TEST_EO_Continue); |
|
||||||
VoteYes(model, "6", TEST_EO_Continue); |
|
||||||
VoteYes(model, "3", TEST_EO_Continue); |
|
||||||
VoteNo(model, "5", TEST_EO_Continue); |
|
||||||
VoteYes(model, "5", TEST_EO_Continue); |
|
||||||
VoteNo(model, "5", TEST_EO_Continue); |
|
||||||
VoteYes(model, "4", TEST_EO_Continue); |
|
||||||
VoteNo(model, "6", TEST_EO_Continue); |
|
||||||
VoteYes(model, "7", TEST_EO_Continue); |
|
||||||
SetVoters(model, "2", "4", "5", "6", "8", "9", "10"); // remove 1, 3, 7 - 3 "yes" votes |
|
||||||
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
|
||||||
VoteNo(model, "8", TEST_EO_Continue); |
|
||||||
VoteYes(model, "5", TEST_EO_Continue); |
|
||||||
VoteNo(model, "9", TEST_EO_Continue); |
|
||||||
// Here we're at 3 "no" votes, 3 "yes" votes out of 7 total; |
|
||||||
// disconnect "6" and "9" for "yes" to win |
|
||||||
SetVoters(model, "2", "4", "5", "8", "10"); |
|
||||||
Issue("Unexpected result after voting users disconnected."); |
|
||||||
TEST_ExpectTrue(model.GetStatus() == VPM_Success); |
|
||||||
} |
|
||||||
|
|
||||||
protected static function SubTest_ReconnectVoting_DrawMeansWin() { |
|
||||||
local VotingModel model; |
|
||||||
|
|
||||||
Context("Testing \"reconnect\" voting when draw means victory."); |
|
||||||
model = MakeVotingModel(true); |
|
||||||
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
|
||||||
VoteYes(model, "1", TEST_EO_Continue); |
|
||||||
VoteNo(model, "2", TEST_EO_Continue); |
|
||||||
VoteYes(model, "3", TEST_EO_Continue); |
|
||||||
VoteNo(model, "4", TEST_EO_Continue); |
|
||||||
VoteYes(model, "5", TEST_EO_Continue); |
|
||||||
VoteNo(model, "6", TEST_EO_Continue); |
|
||||||
// Disconnect 1 3 "yes" voters |
|
||||||
SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10"); |
|
||||||
VoteYes(model, "7", TEST_EO_Continue); |
|
||||||
VoteNo(model, "8", TEST_EO_Continue); |
|
||||||
VoteYes(model, "9", TEST_EO_Continue); |
|
||||||
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
|
||||||
MakeFaultyNoVote(model, "3", VFR_NotAllowed); |
|
||||||
// Restore 3 "yes" voter |
|
||||||
SetVoters(model, "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
|
||||||
VoteNo(model, "3", TEST_EO_End); |
|
||||||
} |
|
||||||
|
|
||||||
protected static function SubTest_ReconnectVoting_DrawMeansLoss() { |
|
||||||
local VotingModel model; |
|
||||||
|
|
||||||
Context("Testing \"reconnect\" voting when draw means loss."); |
|
||||||
model = MakeVotingModel(false); |
|
||||||
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
|
||||||
VoteYes(model, "1", TEST_EO_Continue); |
|
||||||
VoteNo(model, "2", TEST_EO_Continue); |
|
||||||
VoteYes(model, "3", TEST_EO_Continue); |
|
||||||
VoteNo(model, "4", TEST_EO_Continue); |
|
||||||
VoteYes(model, "5", TEST_EO_Continue); |
|
||||||
VoteNo(model, "6", TEST_EO_Continue); |
|
||||||
// Disconnect 1 3 "yes" voters |
|
||||||
SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10"); |
|
||||||
VoteYes(model, "7", TEST_EO_Continue); |
|
||||||
VoteYes(model, "9", TEST_EO_Continue); |
|
||||||
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
|
||||||
MakeFaultyNoVote(model, "3", VFR_NotAllowed); |
|
||||||
// Restore 3 "yes" voter |
|
||||||
SetVoters(model, "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
|
||||||
VoteNo(model, "8", TEST_EO_Continue); |
|
||||||
VoteNo(model, "3", TEST_EO_End); |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
caseGroup = "Commands" |
|
||||||
caseName = "Voting model" |
|
||||||
} |
|
@ -1,306 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CmdItemsTool extends AcediaObject |
|
||||||
dependson(CommandAPI) |
|
||||||
abstract; |
|
||||||
|
|
||||||
//! This is a base class for auxiliary objects that will be used for storing |
|
||||||
//! named [`Command`] instances and [`Voting`] classes: they both have in common |
|
||||||
//! the need to remember who was authorized to use them (i.e. which user group) |
|
||||||
//! and with what permissions (i.e. name of the config that contains appropriate |
|
||||||
//! permissions). |
|
||||||
//! |
|
||||||
//! Aside from trivial accessors to its data, it also provides a way to resolve |
|
||||||
//! the best permissions available to the user by finding the most priviledged |
|
||||||
//! group he belongs to. |
|
||||||
//! |
|
||||||
//! NOTE: child classes must implement `MakeCard()` method and can override |
|
||||||
//! `DiscardCard()` method to catch events of removing items from storage. |
|
||||||
|
|
||||||
/// Allows to specify a base class requirement for this tool - only classes |
|
||||||
/// that were derived from it can be stored inside. |
|
||||||
var protected const class<AcediaObject> ruleBaseClass; |
|
||||||
|
|
||||||
/// Names of user groups that can decide permissions for items, |
|
||||||
/// in order of importance: from most significant to the least significant. |
|
||||||
/// This is used for resolving the best permissions for each user. |
|
||||||
var private array<Text> permissionGroupOrder; |
|
||||||
|
|
||||||
/// Maps item names to their [`ItemCards`] with information about which groups |
|
||||||
/// are authorized to use this particular item. |
|
||||||
var private HashTable registeredCards; |
|
||||||
|
|
||||||
var LoggerAPI.Definition errItemInvalidName; |
|
||||||
var LoggerAPI.Definition errItemDuplicate; |
|
||||||
|
|
||||||
protected function Constructor() { |
|
||||||
registeredCards = _.collections.EmptyHashTable(); |
|
||||||
} |
|
||||||
|
|
||||||
protected function Finalizer() { |
|
||||||
_.memory.Free(registeredCards); |
|
||||||
_.memory.FreeMany(permissionGroupOrder); |
|
||||||
registeredCards = none; |
|
||||||
permissionGroupOrder.length = 0; |
|
||||||
} |
|
||||||
|
|
||||||
/// Registers given item class under the specified (case-insensitive) name. |
|
||||||
/// |
|
||||||
/// If name parameter is omitted (specified as `none`) or is an invalid name |
|
||||||
/// (according to [`BaseText::IsValidName()`] method), then item class will not |
|
||||||
/// be registered. |
|
||||||
/// |
|
||||||
/// Returns `true` if item was successfully registered and `false` otherwise`. |
|
||||||
/// |
|
||||||
/// # Errors |
|
||||||
/// |
|
||||||
/// If provided name that is invalid or already taken by a different item - |
|
||||||
/// a warning will be logged and item class won't be registered. |
|
||||||
public function bool AddItemClass(class<AcediaObject> itemClass, BaseText itemName) { |
|
||||||
local Text itemKey; |
|
||||||
local ItemCard newCard, existingCard; |
|
||||||
|
|
||||||
if (itemClass == none) return false; |
|
||||||
if (itemName == none) return false; |
|
||||||
if (registeredCards == none) return false; |
|
||||||
|
|
||||||
if (ruleBaseClass == none || !ClassIsChildOf(itemClass, ruleBaseClass)) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
// The item name is transformed into lowercase, immutable value. |
|
||||||
// This facilitates the use of item names as keys in a [`HashTable`], |
|
||||||
// enabling case-insensitive matching. |
|
||||||
itemKey = itemName.LowerCopy(); |
|
||||||
if (itemKey == none || !itemKey.IsValidName()) { |
|
||||||
_.logger.Auto(errItemInvalidName).ArgClass(itemClass).Arg(itemKey); |
|
||||||
return false; |
|
||||||
} |
|
||||||
// Guaranteed to only store cards |
|
||||||
existingCard = ItemCard(registeredCards.GetItem(itemName)); |
|
||||||
if (existingCard != none) { |
|
||||||
_.logger.Auto(errItemDuplicate) |
|
||||||
.ArgClass(existingCard.GetItemClass()) |
|
||||||
.Arg(itemKey) |
|
||||||
.ArgClass(itemClass); |
|
||||||
_.memory.Free(existingCard); |
|
||||||
return false; |
|
||||||
} |
|
||||||
newCard = MakeCard(itemClass, itemName); |
|
||||||
registeredCards.SetItem(itemKey, newCard); |
|
||||||
_.memory.Free2(itemKey, newCard); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
/// Removes item of given class from the list of registered items. |
|
||||||
/// |
|
||||||
/// Removing once registered item is not an action that is expected to |
|
||||||
/// be performed under normal circumstances and does not have an efficient |
|
||||||
/// implementation (it is linear on the current amount of items). |
|
||||||
/// |
|
||||||
/// Returns `true` if successfully removed registered item class and |
|
||||||
/// `false` otherwise (either item wasn't registered or caller tool |
|
||||||
/// initialized). |
|
||||||
public function bool RemoveItemClass(class<AcediaObject> itemClass) { |
|
||||||
local int i; |
|
||||||
local CollectionIterator iter; |
|
||||||
local ItemCard nextCard; |
|
||||||
local array<Text> keysToRemove; |
|
||||||
|
|
||||||
if (itemClass == none) return false; |
|
||||||
if (registeredCards == none) return false; |
|
||||||
|
|
||||||
// Removing items during iterator breaks an iterator, so first we find |
|
||||||
// all the keys to remove |
|
||||||
iter = registeredCards.Iterate(); |
|
||||||
iter.LeaveOnlyNotNone(); |
|
||||||
while (!iter.HasFinished()) { |
|
||||||
// Guaranteed to only be `ItemCard` |
|
||||||
nextCard = ItemCard(iter.Get()); |
|
||||||
if (nextCard.GetItemClass() == itemClass) { |
|
||||||
keysToRemove[keysToRemove.length] = Text(iter.GetKey()); |
|
||||||
DiscardCard(nextCard); |
|
||||||
} |
|
||||||
_.memory.Free(nextCard); |
|
||||||
iter.Next(); |
|
||||||
} |
|
||||||
iter.FreeSelf(); |
|
||||||
// Actual clean up everything in `keysToRemove` |
|
||||||
for (i = 0; i < keysToRemove.length; i += 1) { |
|
||||||
registeredCards.RemoveItem(keysToRemove[i]); |
|
||||||
} |
|
||||||
_.memory.FreeMany(keysToRemove); |
|
||||||
return (keysToRemove.length > 0); |
|
||||||
} |
|
||||||
|
|
||||||
/// Allows to specify the order of the user group in terms of privilege for |
|
||||||
/// accessing stored items. Only specified groups will be used when resolving |
|
||||||
/// appropriate permissions config name for a user. |
|
||||||
public final function SetPermissionGroupOrder(array<Text> groupOrder) { |
|
||||||
local int i; |
|
||||||
|
|
||||||
_.memory.FreeMany(permissionGroupOrder); |
|
||||||
permissionGroupOrder.length = 0; |
|
||||||
for (i = 0; i < groupOrder.length; i += 1) { |
|
||||||
if (groupOrder[i] != none) { |
|
||||||
permissionGroupOrder[permissionGroupOrder.length] = groupOrder[i].Copy(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// Specifies what permissions (given by the config name) given user group has |
|
||||||
/// when using an item with a specified name. |
|
||||||
/// |
|
||||||
/// Method must be called after item with a given name is added. |
|
||||||
/// |
|
||||||
/// If this config name is specified as `none`, then "default" will be |
|
||||||
/// used instead. For non-`none` values, only an invalid name (according to |
|
||||||
/// [`BaseText::IsValidName()`] method) will prevent the group from being |
|
||||||
/// registered. |
|
||||||
/// |
|
||||||
/// Method will return `true` if group was successfully authorized and `false` |
|
||||||
/// otherwise (either group already authorized or no item with specified name |
|
||||||
/// was added in the caller tool so far). |
|
||||||
/// |
|
||||||
/// # Errors |
|
||||||
/// |
|
||||||
/// If specified group was already authorized to use card's item, then it |
|
||||||
/// will log a warning message about it. |
|
||||||
public function bool AuthorizeUsage(BaseText itemName, BaseText groupName, BaseText configName) { |
|
||||||
local bool result; |
|
||||||
local ItemCard relevantCard; |
|
||||||
|
|
||||||
if (configName != none && !configName.IsValidName()) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
relevantCard = GetCard(itemName); |
|
||||||
if (relevantCard != none) { |
|
||||||
result = relevantCard.AuthorizeGroupWithConfig(groupName, configName); |
|
||||||
_.memory.Free(relevantCard); |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns struct with item class (+ instance, if one was stored) for a given |
|
||||||
/// case in-sensitive item name and name of the config with best permissions |
|
||||||
/// available to the player with provided ID. |
|
||||||
/// |
|
||||||
/// Function only returns `none` for item class if item with a given name |
|
||||||
/// wasn't found. |
|
||||||
/// Config name being `none` with non-`none` item class in the result means |
|
||||||
/// that user with provided ID doesn't have permissions for using the item at |
|
||||||
/// all. |
|
||||||
public final function CommandAPI.ItemConfigInfo ResolveItem(BaseText itemName, BaseText textID) { |
|
||||||
local int i; |
|
||||||
local ItemCard relevantCard; |
|
||||||
local CommandAPI.ItemConfigInfo result; |
|
||||||
|
|
||||||
relevantCard = GetCard(itemName); |
|
||||||
if (relevantCard == none) { |
|
||||||
// At this point contains `none` for all values -> indicates a failure |
|
||||||
// to find item in storage |
|
||||||
return result; |
|
||||||
} |
|
||||||
result.instance = relevantCard.GetItem(); |
|
||||||
result.class = relevantCard.GetItemClass(); |
|
||||||
if (textID == none) { |
|
||||||
return result; |
|
||||||
} |
|
||||||
// Look through all `permissionGroupOrder` in order to find most priviledged |
|
||||||
// group that user with `textID` belongs to |
|
||||||
for (i = 0; i < permissionGroupOrder.length && result.configName == none; i += 1) { |
|
||||||
if (_.users.IsSteamIDInGroup(textID, permissionGroupOrder[i])) { |
|
||||||
result.configName = relevantCard.GetConfigNameForGroup(permissionGroupOrder[i]); |
|
||||||
} |
|
||||||
} |
|
||||||
_.memory.Free(relevantCard); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns all item classes that are stored inside caller tool. |
|
||||||
/// |
|
||||||
/// Doesn't check for duplicates (although with a normal usage, there shouldn't |
|
||||||
/// be any). |
|
||||||
public final function array< class<AcediaObject> > GetAllItemClasses() { |
|
||||||
local array< class<AcediaObject> > result; |
|
||||||
local ItemCard value; |
|
||||||
local CollectionIterator iter; |
|
||||||
|
|
||||||
for (iter = registeredCards.Iterate(); !iter.HasFinished(); iter.Next()) { |
|
||||||
value = ItemCard(iter.Get()); |
|
||||||
if (value != none) { |
|
||||||
result[result.length] = value.GetItemClass(); |
|
||||||
} |
|
||||||
_.memory.Free(value); |
|
||||||
} |
|
||||||
iter.FreeSelf(); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns array of names of all available items. |
|
||||||
public final function array<Text> GetItemsNames() { |
|
||||||
local array<Text> emptyResult; |
|
||||||
|
|
||||||
if (registeredCards != none) { |
|
||||||
return registeredCards.GetTextKeys(); |
|
||||||
} |
|
||||||
return emptyResult; |
|
||||||
} |
|
||||||
|
|
||||||
/// Called each time a new card is to be created and stored. |
|
||||||
/// |
|
||||||
/// Must be reimplemented by child classes. |
|
||||||
protected function ItemCard MakeCard(class<AcediaObject> itemClass, BaseText itemName) { |
|
||||||
return none; |
|
||||||
} |
|
||||||
|
|
||||||
/// Called each time a certain card is to be removed from storage. |
|
||||||
/// |
|
||||||
/// Must be reimplemented by child classes |
|
||||||
/// (reimplementations SHOULD NOT DEALLOCATE `toDiscard`). |
|
||||||
protected function DiscardCard(ItemCard toDiscard) { |
|
||||||
} |
|
||||||
|
|
||||||
/// Find item card for the item that was stored with a specified |
|
||||||
/// case-insensitive name |
|
||||||
/// |
|
||||||
/// Function only returns `none` if item with a given name wasn't found |
|
||||||
/// (or `none` was provided as an argument). |
|
||||||
protected final function ItemCard GetCard(BaseText itemName) { |
|
||||||
local Text itemKey; |
|
||||||
local ItemCard relevantCard; |
|
||||||
|
|
||||||
if (itemName == none) return none; |
|
||||||
if (registeredCards == none) return none; |
|
||||||
|
|
||||||
/// The item name is transformed into lowercase, immutable value. |
|
||||||
/// This facilitates the use of item names as keys in a [`HashTable`], |
|
||||||
/// enabling case-insensitive matching. |
|
||||||
itemKey = itemName.LowerCopy(); |
|
||||||
relevantCard = ItemCard(registeredCards.GetItem(itemKey)); |
|
||||||
_.memory.Free(itemKey); |
|
||||||
return relevantCard; |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
errItemInvalidName = (l=LOG_Error,m="Attempt at registering item with class `%1` under an invalid name \"%2\" will be ignored.") |
|
||||||
errItemDuplicate = (l=LOG_Error,m="Command `%1` is already registered with name '%2'. Attempt at registering command `%3` with the same name will be ignored.") |
|
||||||
} |
|
@ -1,142 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class CommandsTool extends CmdItemsTool; |
|
||||||
|
|
||||||
//! This is a base class for auxiliary objects that will be used for storing |
|
||||||
//! named [`Command`] instances. |
|
||||||
//! |
|
||||||
//! This storage class allows for efficient manipulation and retrieval of |
|
||||||
//! [`Command`]s, along with information about what use groups were authorized |
|
||||||
//! to use them. |
|
||||||
//! |
|
||||||
//! Additionally, this tool allows for efficient fetching of commands that |
|
||||||
//! belong to a particular *command group*. |
|
||||||
|
|
||||||
/// [`HashTable`] that maps a command group name to a set of command names that |
|
||||||
/// belong to it. |
|
||||||
var private HashTable groupedCommands; |
|
||||||
|
|
||||||
protected function Constructor() { |
|
||||||
super.Constructor(); |
|
||||||
groupedCommands = _.collections.EmptyHashTable(); |
|
||||||
} |
|
||||||
|
|
||||||
protected function Finalizer() { |
|
||||||
super.Finalizer(); |
|
||||||
_.memory.Free(groupedCommands); |
|
||||||
groupedCommands = none; |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns all known command groups' names. |
|
||||||
public final function array<Text> GetGroupsNames() { |
|
||||||
local array<Text> emptyResult; |
|
||||||
|
|
||||||
if (groupedCommands != none) { |
|
||||||
return groupedCommands.GetTextKeys(); |
|
||||||
} |
|
||||||
return emptyResult; |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns array of names of all available commands belonging to the specified |
|
||||||
/// group. |
|
||||||
public final function array<Text> GetCommandNamesInGroup(BaseText groupName) { |
|
||||||
local int i; |
|
||||||
local ArrayList commandNamesArray; |
|
||||||
local array<Text> result; |
|
||||||
|
|
||||||
if (groupedCommands == none) return result; |
|
||||||
commandNamesArray = groupedCommands.GetArrayList(groupName); |
|
||||||
if (commandNamesArray == none) return result; |
|
||||||
|
|
||||||
for (i = 0; i < commandNamesArray.GetLength(); i += 1) { |
|
||||||
result[result.length] = commandNamesArray.GetText(i); |
|
||||||
} |
|
||||||
_.memory.Free(commandNamesArray); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
protected function ItemCard MakeCard(class<AcediaObject> commandClass, BaseText itemName) { |
|
||||||
local Command newCommandInstance; |
|
||||||
local ItemCard newCard; |
|
||||||
local Text commandGroup; |
|
||||||
|
|
||||||
if (class<Command>(commandClass) != none) { |
|
||||||
newCommandInstance = Command(_.memory.Allocate(commandClass, true)); |
|
||||||
newCommandInstance.Initialize(itemName); |
|
||||||
newCard = ItemCard(_.memory.Allocate(class'ItemCard')); |
|
||||||
newCard.InitializeWithInstance(newCommandInstance); |
|
||||||
|
|
||||||
// Guaranteed to be lower case (keys of [`HashTable`]) |
|
||||||
if (itemName != none) { |
|
||||||
itemName = itemName.LowerCopy(); |
|
||||||
} else { |
|
||||||
itemName = newCommandInstance.GetPreferredName(); |
|
||||||
} |
|
||||||
commandGroup = newCommandInstance.GetGroupName(); |
|
||||||
AssociateGroupAndName(commandGroup, itemName); |
|
||||||
_.memory.Free3(newCommandInstance, itemName, commandGroup); |
|
||||||
} |
|
||||||
return newCard; |
|
||||||
} |
|
||||||
|
|
||||||
protected function DiscardCard(ItemCard toDiscard) { |
|
||||||
local Text groupKey, commandName; |
|
||||||
local Command storedCommand; |
|
||||||
local ArrayList listOfCommands; |
|
||||||
|
|
||||||
if (toDiscard == none) return; |
|
||||||
// Guaranteed to store a [`Command`] |
|
||||||
storedCommand = Command(toDiscard.GetItem()); |
|
||||||
if (storedCommand == none) return; |
|
||||||
|
|
||||||
// Guaranteed to be stored in a lower case |
|
||||||
commandName = storedCommand.GetName(); |
|
||||||
listOfCommands = groupedCommands.GetArrayList(groupKey); |
|
||||||
if (listOfCommands != none && commandName != none) { |
|
||||||
listOfCommands.RemoveItem(commandName); |
|
||||||
} |
|
||||||
_.memory.Free2(commandName, storedCommand); |
|
||||||
} |
|
||||||
|
|
||||||
// Expect both arguments to be not `none`. |
|
||||||
// Expect both arguments to be lower-case. |
|
||||||
private final function AssociateGroupAndName(BaseText groupKey, BaseText commandName) { |
|
||||||
local ArrayList listOfCommands; |
|
||||||
|
|
||||||
if (groupedCommands != none) { |
|
||||||
listOfCommands = groupedCommands.GetArrayList(groupKey); |
|
||||||
if (listOfCommands == none) { |
|
||||||
listOfCommands = _.collections.EmptyArrayList(); |
|
||||||
} |
|
||||||
if (listOfCommands.Find(commandName) < 0) { |
|
||||||
// `< 0` means not found |
|
||||||
listOfCommands.AddItem(commandName); |
|
||||||
} |
|
||||||
// Set `listOfCommands` in case we've just created that array. |
|
||||||
// Won't do anything if it is already recorded there. |
|
||||||
groupedCommands.SetItem(groupKey, listOfCommands); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
ruleBaseClass = class'Command'; |
|
||||||
} |
|
@ -1,177 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class ItemCard extends AcediaObject; |
|
||||||
|
|
||||||
//! Utility class designed for storing either class of an object |
|
||||||
//! (possibly also a specific instance) along with authorization information: |
|
||||||
//! which user groups are allowed to use stored entity and with what level of |
|
||||||
//! permissions (defined by the name of a config with permissions). |
|
||||||
//! |
|
||||||
//! [`ItemCard`] has to be initialized with either [`InitializeWithClass()`] or |
|
||||||
//! [`InitializeWithInstance()`] before it can be used. |
|
||||||
|
|
||||||
/// Class of object that this card describes. |
|
||||||
var private class<AcediaObject> storedClass; |
|
||||||
/// Instance of an object (can also *optionally* be stored in this card) |
|
||||||
var private AcediaObject storedInstance; |
|
||||||
|
|
||||||
/// This [`HashTable`] maps authorized groups to their respective config names. |
|
||||||
/// |
|
||||||
/// Each key represents an authorized group, and its corresponding value |
|
||||||
/// indicates the associated config name. If a key has a value of `none`, |
|
||||||
/// the default config (named "default") should be used for that group. |
|
||||||
var private HashTable groupToConfig; |
|
||||||
|
|
||||||
var LoggerAPI.Definition errGroupAlreadyHasConfig; |
|
||||||
|
|
||||||
protected function Finalizer() { |
|
||||||
_.memory.Free2(storedInstance, groupToConfig); |
|
||||||
storedInstance = none; |
|
||||||
storedClass = none; |
|
||||||
groupToConfig = none; |
|
||||||
} |
|
||||||
|
|
||||||
/// Initializes the caller [`ItemCard`] object with class to be stored. |
|
||||||
/// |
|
||||||
/// Initialization can only be done once: once method returned `true`, |
|
||||||
/// all future calls will fail. |
|
||||||
/// |
|
||||||
/// Returns `false` if caller was already initialized or `none` is provided as |
|
||||||
/// an argument. Otherwise succeeds and returns `true`. |
|
||||||
public function bool InitializeWithClass(class<AcediaObject> toStore) { |
|
||||||
if (storedClass != none) return false; |
|
||||||
if (toStore == none) return false; |
|
||||||
|
|
||||||
storedClass = toStore; |
|
||||||
groupToConfig = _.collections.EmptyHashTable(); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
/// Initializes the caller [`ItemCard`] object with an object to be stored. |
|
||||||
/// |
|
||||||
/// Initialization can only be done once: once method returned `true`, |
|
||||||
/// all future calls will fail. |
|
||||||
/// |
|
||||||
/// Returns `false` caller was already initialized or `none` is provided as |
|
||||||
/// an argument. Otherwise succeeds and returns `true`. |
|
||||||
public function bool InitializeWithInstance(AcediaObject toStore) { |
|
||||||
if (storedClass != none) return false; |
|
||||||
if (toStore == none) return false; |
|
||||||
|
|
||||||
storedClass = toStore.class; |
|
||||||
storedInstance = toStore; |
|
||||||
storedInstance.NewRef(); |
|
||||||
groupToConfig = _.collections.EmptyHashTable(); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
/// Authorizes a new group to use the this card's item. |
|
||||||
/// |
|
||||||
/// This function allows to specify the config name for a particular user group. |
|
||||||
/// If this config name is skipped (specified as `none`), then "default" will be |
|
||||||
/// used instead. |
|
||||||
/// |
|
||||||
/// Function will return `true` if group was successfully authorized and |
|
||||||
/// `false` otherwise (either group already authorized or caller [`ItemCard`] |
|
||||||
/// isn't initialized). |
|
||||||
/// |
|
||||||
/// # Errors |
|
||||||
/// |
|
||||||
/// If specified group was already authorized to use card's item, then it |
|
||||||
/// will log an error message about it. |
|
||||||
public function bool AuthorizeGroupWithConfig(BaseText groupName, optional BaseText configName) { |
|
||||||
local Text itemKey; |
|
||||||
local Text storedConfigName; |
|
||||||
|
|
||||||
if (storedClass == none) return false; |
|
||||||
if (groupToConfig == none) return false; |
|
||||||
if (groupName == none) return false; |
|
||||||
if (groupName.IsEmpty()) return false; |
|
||||||
|
|
||||||
/// Make group name immutable and have its characters have a uniform case to |
|
||||||
/// be usable as case-insensitive keys for [`HashTable`]. |
|
||||||
itemKey = groupName.LowerCopy(); |
|
||||||
storedConfigName = groupToConfig.GetText(itemKey); |
|
||||||
if (storedConfigName != none) { |
|
||||||
_.logger.Auto(errGroupAlreadyHasConfig) |
|
||||||
.ArgClass(storedClass) |
|
||||||
.Arg(groupName.Copy()) |
|
||||||
.Arg(storedConfigName) |
|
||||||
.Arg(configName.Copy()); |
|
||||||
_.memory.Free(itemKey); |
|
||||||
return false; |
|
||||||
} |
|
||||||
// We don't actually record "default" value at this point, instead opting |
|
||||||
// to return "default" in getter functions in case stored `configName` |
|
||||||
// is `none`. |
|
||||||
groupToConfig.SetItem(itemKey, configName); |
|
||||||
_.memory.Free(itemKey); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns item instance for the caller [`ItemCard`]. |
|
||||||
/// |
|
||||||
/// Returns `none` iff this card wasn't initialized with an instance. |
|
||||||
public function AcediaObject GetItem() { |
|
||||||
if (storedInstance != none) { |
|
||||||
storedInstance.NewRef(); |
|
||||||
} |
|
||||||
return storedInstance; |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns item class for the caller [`ItemCard`]. |
|
||||||
/// |
|
||||||
/// Returns `none` iff this card wasn't initialized. |
|
||||||
public function class<AcediaObject> GetItemClass() { |
|
||||||
return storedClass; |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns the name of config that was authorized for the specified group. |
|
||||||
/// |
|
||||||
/// Returns `none` if group wasn't authorized, otherwise guaranteed to |
|
||||||
/// return non-`none` and non-empty `Text` value. |
|
||||||
public function Text GetConfigNameForGroup(BaseText groupName) { |
|
||||||
local Text groupNameAsKey, result; |
|
||||||
|
|
||||||
if (storedClass == none) return none; |
|
||||||
if (groupToConfig == none) return none; |
|
||||||
if (groupName == none) return none; |
|
||||||
|
|
||||||
/// Make group name immutable and have its characters a uniform case to |
|
||||||
/// be usable as case-insensitive keys for [`HashTable`] |
|
||||||
groupNameAsKey = groupName.LowerCopy(); |
|
||||||
if (groupToConfig.HasKey(groupNameAsKey)) { |
|
||||||
result = groupToConfig.GetText(groupNameAsKey); |
|
||||||
if (result == none) { |
|
||||||
// If we do have specified group recorded as a key, then we must |
|
||||||
// return non-`none` config name, defaulting to "default" value |
|
||||||
// if none was provided |
|
||||||
result = P("default").Copy(); |
|
||||||
} |
|
||||||
} |
|
||||||
_.memory.Free(groupNameAsKey); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
errGroupAlreadyHasConfig = (l=LOG_Error,m="Item `%1` is already added to group '%2' with config '%3'. Attempt to add it with config '%4' is ignored.") |
|
||||||
} |
|
@ -1,119 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
class VotingsTool extends CmdItemsTool |
|
||||||
dependson(CommandAPI); |
|
||||||
|
|
||||||
//! This is a base class for auxiliary objects that will be used for storing |
|
||||||
//! named [`Voting`] classes. |
|
||||||
//! |
|
||||||
//! This storage class allows for efficient manipulation and retrieval of |
|
||||||
//! [`Voting`] classes, along with information about what use groups were |
|
||||||
//! authorized to use them. |
|
||||||
//! |
|
||||||
//! Additionally this tool is used to keep track of the currently ongoing |
|
||||||
//! voting, preventing [`CommandsAPI`] from starting several votings at once. |
|
||||||
|
|
||||||
/// Currently running voting process. |
|
||||||
/// This tool doesn't actively track when voting ends, so reference can be |
|
||||||
/// non-`none` even if voting has already ended. Instead `DropFinishedVoting()` |
|
||||||
/// method is used as needed to figure out whether that voting has ended and |
|
||||||
/// should be deallocated. |
|
||||||
var private Voting currentVoting; |
|
||||||
|
|
||||||
protected function Finalizer() { |
|
||||||
super.Finalizer(); |
|
||||||
_.memory.Free(currentVoting); |
|
||||||
currentVoting = none; |
|
||||||
} |
|
||||||
|
|
||||||
/// Starts a voting process with a given name, returning its result. |
|
||||||
public final function CommandAPI.StartVotingResult StartVoting( |
|
||||||
CommandAPI.VotingConfigInfo votingData, |
|
||||||
HashTable arguments |
|
||||||
) { |
|
||||||
local CommandAPI.StartVotingResult result; |
|
||||||
DropFinishedVoting(); |
|
||||||
if (currentVoting != none) { |
|
||||||
return SVR_AlreadyInProgress; |
|
||||||
} |
|
||||||
if (votingData.votingClass == none) { |
|
||||||
return SVR_UnknownVoting; |
|
||||||
} |
|
||||||
currentVoting = Voting(_.memory.Allocate(votingData.votingClass)); |
|
||||||
result = currentVoting.Start(votingData.config, arguments); |
|
||||||
if (result != SVR_Success) { |
|
||||||
_.memory.Free(currentVoting); |
|
||||||
currentVoting = none; |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns `true` iff some voting is currently active. |
|
||||||
public final function bool IsVotingRunning() { |
|
||||||
DropFinishedVoting(); |
|
||||||
return (currentVoting != none); |
|
||||||
} |
|
||||||
|
|
||||||
/// Returns instance of the active voting. |
|
||||||
/// |
|
||||||
/// `none` iff no voting is currently active. |
|
||||||
public final function Voting GetCurrentVoting() { |
|
||||||
DropFinishedVoting(); |
|
||||||
if (currentVoting != none) { |
|
||||||
currentVoting.NewRef(); |
|
||||||
} |
|
||||||
return currentVoting; |
|
||||||
} |
|
||||||
|
|
||||||
protected function ItemCard MakeCard(class<AcediaObject> votingClass, BaseText itemName) { |
|
||||||
local ItemCard newCard; |
|
||||||
|
|
||||||
if (class<Voting>(votingClass) != none) { |
|
||||||
newCard = ItemCard(_.memory.Allocate(class'ItemCard')); |
|
||||||
newCard.InitializeWithClass(votingClass); |
|
||||||
} |
|
||||||
return newCard; |
|
||||||
} |
|
||||||
|
|
||||||
private final function class<Voting> GetVoting(BaseText itemName) { |
|
||||||
local ItemCard relevantCard; |
|
||||||
local class<Voting> result; |
|
||||||
|
|
||||||
relevantCard = GetCard(itemName); |
|
||||||
if (relevantCard != none) { |
|
||||||
result = class<Voting>(relevantCard.GetItemClass()); |
|
||||||
} |
|
||||||
_.memory.Free(relevantCard); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
// Clears `currentVoting` if it has already finished |
|
||||||
private final function DropFinishedVoting() { |
|
||||||
if (currentVoting != none && currentVoting.HasEnded()) { |
|
||||||
_.memory.Free(currentVoting); |
|
||||||
currentVoting = none; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
defaultproperties { |
|
||||||
ruleBaseClass = class'Voting' |
|
||||||
} |
|
@ -0,0 +1,563 @@ |
|||||||
|
/** |
||||||
|
* 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 <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class TEST_Voting extends TestCase |
||||||
|
abstract |
||||||
|
dependsOn(VotingModel); |
||||||
|
|
||||||
|
enum ExpectedOutcome { |
||||||
|
TEST_EO_Continue, |
||||||
|
TEST_EO_End, |
||||||
|
TEST_EO_EndDraw, |
||||||
|
}; |
||||||
|
|
||||||
|
protected static function VotingModel MakeVotingModel(VotingModel.VotingPolicies policies) { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
model = VotingModel(__().memory.Allocate(class'VotingModel')); |
||||||
|
model.Initialize(policies); |
||||||
|
return model; |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SetVoters( |
||||||
|
VotingModel model, |
||||||
|
optional string voterID0, |
||||||
|
optional string voterID1, |
||||||
|
optional string voterID2, |
||||||
|
optional string voterID3, |
||||||
|
optional string voterID4, |
||||||
|
optional string voterID5, |
||||||
|
optional string voterID6, |
||||||
|
optional string voterID7, |
||||||
|
optional string voterID8, |
||||||
|
optional string voterID9 |
||||||
|
) { |
||||||
|
local UserID nextID; |
||||||
|
local array<UserID> voterIDs; |
||||||
|
|
||||||
|
if (voterID0 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID0)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID1 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID1)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID2 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID2)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID3 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID3)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID4 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID4)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID5 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID5)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID6 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID6)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID7 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID7)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID8 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID8)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID9 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID9)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
model.UpdatePotentialVoters(voterIDs); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function MakeFaultyYesVote( |
||||||
|
VotingModel model, |
||||||
|
string voterID, |
||||||
|
VotingModel.VotingResult expected) { |
||||||
|
local UserID id; |
||||||
|
|
||||||
|
id = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
id.Initialize(__().text.FromString(voterID)); |
||||||
|
Issue("Illegal vote had unexpected result."); |
||||||
|
TEST_ExpectTrue(model.CastVote(id, true) == expected); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function MakeFaultyNoVote( |
||||||
|
VotingModel model, |
||||||
|
string voterID, |
||||||
|
VotingModel.VotingResult expected) { |
||||||
|
local UserID id; |
||||||
|
|
||||||
|
id = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
id.Initialize(__().text.FromString(voterID)); |
||||||
|
Issue("Illegal vote had unexpected result."); |
||||||
|
TEST_ExpectTrue(model.CastVote(id, false) == expected); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function VoteYes(VotingModel model, string voterID, ExpectedOutcome expected) { |
||||||
|
local UserID id; |
||||||
|
|
||||||
|
id = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
id.Initialize(__().text.FromString(voterID)); |
||||||
|
Issue("Failed to add legitimate vote."); |
||||||
|
TEST_ExpectTrue(model.CastVote(id, true) == VFR_Success); |
||||||
|
if (expected == TEST_EO_Continue) { |
||||||
|
Issue("Vote, that shouldn't have ended voting, ended it."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
||||||
|
} else if (expected == TEST_EO_End) { |
||||||
|
Issue("Vote, that should've ended voting with one side's victory, didn't do it."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Success); |
||||||
|
} else if (expected == TEST_EO_EndDraw) { |
||||||
|
Issue("Vote, that should've ended voting with a draw, didn't do it."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Draw); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static function VoteNo(VotingModel model, string voterID, ExpectedOutcome expected) { |
||||||
|
local UserID id; |
||||||
|
|
||||||
|
id = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
id.Initialize(__().text.FromString(voterID)); |
||||||
|
Issue("Failed to add legitimate vote."); |
||||||
|
TEST_ExpectTrue(model.CastVote(id, false) == VFR_Success); |
||||||
|
if (expected == TEST_EO_Continue) { |
||||||
|
Issue("Vote, that shouldn't have ended voting, ended it."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
||||||
|
} else if (expected == TEST_EO_End) { |
||||||
|
Issue("Vote, that should've ended voting with one side's victory, didn't do it."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Failure); |
||||||
|
} else if (expected == TEST_EO_EndDraw) { |
||||||
|
Issue("Vote, that should've ended voting with a draw, didn't do it."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Draw); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static function TESTS() { |
||||||
|
Test_RestrictiveVoting(); |
||||||
|
Test_CanLeaveVoting(); |
||||||
|
Test_CanChangeVoting(); |
||||||
|
Test_All(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_RestrictiveVoting() { |
||||||
|
SubTest_RestrictiveYesVoting(); |
||||||
|
SubTest_RestrictiveNoVoting(); |
||||||
|
SubTest_RestrictiveDrawVoting(); |
||||||
|
SubTest_RestrictiveFaultyVoting(); |
||||||
|
SubTest_RestrictiveDisconnectVoting(); |
||||||
|
SubTest_RestrictiveReconnectVoting(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_RestrictiveYesVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing restrictive \"yes\" voting."); |
||||||
|
model = MakeVotingModel(VP_Restrictive); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "1", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_RestrictiveNoVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing restrictive \"no\" voting."); |
||||||
|
model = MakeVotingModel(VP_Restrictive); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteNo(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_RestrictiveDrawVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing restrictive \"draw\" voting."); |
||||||
|
model = MakeVotingModel(VP_Restrictive); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_EndDraw); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_RestrictiveFaultyVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing restrictive \"faulty\" voting."); |
||||||
|
model = MakeVotingModel(VP_Restrictive); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
MakeFaultyYesVote(model, "3", VFR_AlreadyVoted); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "7", VFR_NotAllowed); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "7", VFR_NotAllowed); |
||||||
|
MakeFaultyNoVote(model, "5", VFR_AlreadyVoted); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "3", VFR_CannotChangeVote); |
||||||
|
VoteYes(model, "6", TEST_EO_End); |
||||||
|
MakeFaultyYesVote(model, "4", VFR_VotingEnded); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_RestrictiveDisconnectVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing restrictive \"disconnect\" voting."); |
||||||
|
model = MakeVotingModel(VP_Restrictive); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteYes(model, "7", TEST_EO_Continue); |
||||||
|
SetVoters(model, "2", "4", "5", "6", "8", "9", "10"); // remove 1, 3, 7 - 3 "yes" votes |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteYes(model, "9", TEST_EO_Continue); |
||||||
|
// Here we're at 3 "no" votes, 3 "yes" votes out of 7 total; |
||||||
|
// disconnect "2" and "9" for "no" to win |
||||||
|
SetVoters(model, "4", "5", "6", "8", "10"); |
||||||
|
Issue("Unexpected result after voting users disconnected."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Failure); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_RestrictiveReconnectVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing restrictive \"reconnecting\" voting."); |
||||||
|
model = MakeVotingModel(VP_Restrictive); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
// Disconnect 1 3 "yes" voters |
||||||
|
SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "7", TEST_EO_Continue); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteYes(model, "9", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
MakeFaultyNoVote(model, "3", VFR_NotAllowed); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteNo(model, "10", TEST_EO_EndDraw); |
||||||
|
} |
||||||
|
/* Testing restrictive "reconnecting" voting. |
||||||
|
Unexpected result after voting users reconnected. [1] */ |
||||||
|
protected static function Test_CanLeaveVoting() { |
||||||
|
SubTest_CanLeaveYesVoting(); |
||||||
|
SubTest_CanLeaveNoVoting(); |
||||||
|
SubTest_CanLeaveDrawVoting(); |
||||||
|
SubTest_CanLeaveFaultyVoting(); |
||||||
|
SubTest_CanLeaveDisconnectVoting(); |
||||||
|
SubTest_CanLeaveReconnectVoting(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanLeaveYesVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"yes\" voting where users are allowed to leave."); |
||||||
|
model = MakeVotingModel(VP_CanLeave); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
SetVoters(model, "1", "5", "6"); |
||||||
|
Issue("Unexpected result after voting users leaves."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
||||||
|
VoteYes(model, "1", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanLeaveNoVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"no\" voting where users are allowed to leave."); |
||||||
|
model = MakeVotingModel(VP_CanLeave); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteNo(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
SetVoters(model, "3", "4", "5"); |
||||||
|
Issue("Unexpected result after voting users leaves."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
||||||
|
VoteNo(model, "4", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanLeaveDrawVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"draw\" voting where users are allowed to leave."); |
||||||
|
model = MakeVotingModel(VP_CanLeave); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
SetVoters(model, "4"); |
||||||
|
Issue("Unexpected result after voting users leaves."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
||||||
|
VoteNo(model, "4", TEST_EO_EndDraw); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanLeaveFaultyVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"faulty\" voting where users are allowed to leave."); |
||||||
|
model = MakeVotingModel(VP_CanLeave); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
MakeFaultyYesVote(model, "3", VFR_AlreadyVoted); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "7", VFR_NotAllowed); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "7", VFR_NotAllowed); |
||||||
|
MakeFaultyNoVote(model, "5", VFR_AlreadyVoted); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "3", VFR_CannotChangeVote); |
||||||
|
SetVoters(model, "4", "5", "6"); |
||||||
|
Issue("Unexpected result after voting users leaves."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
||||||
|
VoteYes(model, "6", TEST_EO_End); |
||||||
|
MakeFaultyYesVote(model, "4", VFR_VotingEnded); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanLeaveDisconnectVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"leave\" voting where users are allowed to leave."); |
||||||
|
model = MakeVotingModel(VP_CanLeave); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteYes(model, "7", TEST_EO_Continue); |
||||||
|
SetVoters(model, "2", "4", "5", "6", "8", "9", "10"); |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteYes(model, "10", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanLeaveReconnectVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"reconnecting\" voting where users are allowed to leave."); |
||||||
|
model = MakeVotingModel(VP_CanLeave); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
// Disconnect 1 3 "yes" voters |
||||||
|
SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "7", TEST_EO_Continue); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteNo(model, "9", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
MakeFaultyNoVote(model, "3", VFR_NotAllowed); |
||||||
|
VoteYes(model, "10", TEST_EO_EndDraw); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_CanChangeVoting() { |
||||||
|
SubTest_CanChangeYesVoting(); |
||||||
|
SubTest_CanChangeNoVoting(); |
||||||
|
SubTest_CanChangeDrawVoting(); |
||||||
|
SubTest_CanChangeFaultyVoting(); |
||||||
|
SubTest_CanChangeDisconnectVoting(); |
||||||
|
SubTest_CanChangeReconnectVoting(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanChangeYesVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"yes\" voting where users are allowed to change their vote."); |
||||||
|
model = MakeVotingModel(VP_CanChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteNo(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "6", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanChangeNoVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"no\" voting where users are allowed to change their vote."); |
||||||
|
model = MakeVotingModel(VP_CanChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteNo(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanChangeDrawVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"draw\" voting where users are allowed to change their vote."); |
||||||
|
model = MakeVotingModel(VP_CanChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_EndDraw); |
||||||
|
} |
||||||
|
protected static function SubTest_CanChangeFaultyVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"faulty\" voting where users are allowed to change their vote."); |
||||||
|
model = MakeVotingModel(VP_CanChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "6", TEST_EO_End); |
||||||
|
MakeFaultyYesVote(model, "4", VFR_VotingEnded); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanChangeDisconnectVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"disconnect\" voting where users are allowed to change their vote."); |
||||||
|
model = MakeVotingModel(VP_CanChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "6", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteYes(model, "7", TEST_EO_Continue); |
||||||
|
SetVoters(model, "2", "4", "5", "6", "8", "9", "10"); // remove 1, 3, 7 - 3 "yes" votes |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "9", TEST_EO_Continue); |
||||||
|
// Here we're at 3 "no" votes, 3 "yes" votes out of 7 total; |
||||||
|
// disconnect "6" and "9" for "yes" to win |
||||||
|
SetVoters(model, "2", "4", "5", "8", "10"); |
||||||
|
Issue("Unexpected result after voting users disconnected."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Success); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanChangeReconnectVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"reconnect\" voting where users are allowed to change their vote."); |
||||||
|
model = MakeVotingModel(VP_CanChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
// Disconnect 1 3 "yes" voters |
||||||
|
SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "7", TEST_EO_Continue); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteYes(model, "9", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
MakeFaultyNoVote(model, "3", VFR_NotAllowed); |
||||||
|
// Restore 3 "yes" voter |
||||||
|
SetVoters(model, "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteNo(model, "3", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_All() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing permissive voting options."); |
||||||
|
model = MakeVotingModel(VP_CanLeaveAndChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
// Disconnect 1 and 5 voters |
||||||
|
SetVoters(model, "2", "3", "4", "6", "7", "8", "9", "10"); |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
MakeFaultyNoVote(model, "5", VFR_NotAllowed); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteNo(model, "7", TEST_EO_Continue); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteNo(model, "9", TEST_EO_Continue); |
||||||
|
// Bring back 1, disconnect 3 and 6 |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "8", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_Continue); |
||||||
|
// Disconnect 10, finishing voting (since now only 9 voters are available) |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9"); |
||||||
|
Issue("Unexpected result after voting users disconnected."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Failure); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties { |
||||||
|
caseGroup = "Commands" |
||||||
|
caseName = "Voting model" |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -1,132 +0,0 @@ |
|||||||
/** |
|
||||||
* 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 <https://www.gnu.org/licenses/>. |
|
||||||
*/ |
|
||||||
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<string> allowedToVoteGroup; |
|
||||||
/// Specifies which group(s) of players are allowed to see who makes what vote. |
|
||||||
var public config array<string> allowedToSeeVotesGroup; |
|
||||||
/// Specifies which group(s) of players are allowed to forcibly end voting. |
|
||||||
var public config array<string> 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" |
|
||||||
} |
|
@ -0,0 +1,75 @@ |
|||||||
|
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<string> allowedToSeeVotesGroup; |
||||||
|
/// Specifies which group(s) of players are allowed to vote. |
||||||
|
var public config array<string> 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" |
||||||
|
} |
Loading…
Reference in new issue