Compare commits
22 Commits
70c41a5926
...
e8ae6fd8d1
Author | SHA1 | Date |
---|---|---|
Anton Tarasenko | e8ae6fd8d1 | 1 year ago |
Anton Tarasenko | 93604c7690 | 1 year ago |
Anton Tarasenko | 159f1dc5a1 | 1 year ago |
Anton Tarasenko | 15b1abc8c3 | 1 year ago |
Anton Tarasenko | ff31ef2472 | 1 year ago |
Anton Tarasenko | c2a8a5c7de | 1 year ago |
Anton Tarasenko | a27e893359 | 1 year ago |
Anton Tarasenko | 0ad28839bb | 1 year ago |
Anton Tarasenko | a26b0adf05 | 1 year ago |
Anton Tarasenko | 80cecd1d20 | 1 year ago |
Anton Tarasenko | 7ff806b104 | 1 year ago |
Anton Tarasenko | be9ba80549 | 1 year ago |
Anton Tarasenko | d7ed4776b4 | 1 year ago |
Anton Tarasenko | a7f1a98548 | 1 year ago |
Anton Tarasenko | 86228a960c | 1 year ago |
Anton Tarasenko | c1dccfc2d6 | 1 year ago |
Anton Tarasenko | 001170e092 | 1 year ago |
Anton Tarasenko | 87c7ee01bb | 1 year ago |
Anton Tarasenko | c76f875620 | 1 year ago |
Anton Tarasenko | 23dc639536 | 1 year ago |
Anton Tarasenko | 7ad3ca55f6 | 1 year ago |
Anton Tarasenko | 757ae39b2e | 1 year ago |
47 changed files with 6417 additions and 2595 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" |
@ -0,0 +1,117 @@ |
|||||||
|
[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
@ -0,0 +1,249 @@ |
|||||||
|
/** |
||||||
|
* Config class for storing map lists. |
||||||
|
* Copyright 2023 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <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' |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
/** |
||||||
|
* Author: dkanus |
||||||
|
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore |
||||||
|
* License: GPL |
||||||
|
* Copyright 2023 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <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 |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
/** |
||||||
|
* 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' |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
/** |
||||||
|
* 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 { |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
/** |
||||||
|
* 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' |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
/** |
||||||
|
* 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 { |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
/** |
||||||
|
* 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' |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
/** |
||||||
|
* 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 { |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
/** |
||||||
|
* 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' |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
/** |
||||||
|
* 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 { |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
/** |
||||||
|
* 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' |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
/** |
||||||
|
* 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 { |
||||||
|
} |
@ -0,0 +1,351 @@ |
|||||||
|
/** |
||||||
|
* 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" |
||||||
|
} |
@ -0,0 +1,306 @@ |
|||||||
|
/** |
||||||
|
* 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.") |
||||||
|
} |
@ -0,0 +1,142 @@ |
|||||||
|
/** |
||||||
|
* 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'; |
||||||
|
} |
@ -0,0 +1,177 @@ |
|||||||
|
/** |
||||||
|
* 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.") |
||||||
|
} |
@ -0,0 +1,119 @@ |
|||||||
|
/** |
||||||
|
* 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' |
||||||
|
} |
@ -1,563 +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, |
|
||||||
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
@ -0,0 +1,132 @@ |
|||||||
|
/** |
||||||
|
* Author: dkanus |
||||||
|
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore |
||||||
|
* License: GPL |
||||||
|
* Copyright 2021-2023 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <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" |
||||||
|
} |
@ -1,75 +0,0 @@ |
|||||||
class VotingSettings extends FeatureConfig |
|
||||||
perobjectconfig |
|
||||||
config(AcediaVoting); |
|
||||||
|
|
||||||
/// Determines the duration of the voting period, specified in seconds. |
|
||||||
var public config float votingTime; |
|
||||||
|
|
||||||
/// Determines whether spectators are allowed to vote. |
|
||||||
var public config bool allowSpectatorVoting; |
|
||||||
/// Specifies which group(s) of players are allowed to see who makes what vote. |
|
||||||
var public config array<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