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. |
||||
; Remember that aliases are case-insensitive. |
||||
[AcediaCore.CommandAliasSource] |
||||
record=(alias="yes",value="vote yes") |
||||
record=(alias="no",value="vote no") |
||||
record=(alias="yes",value="vote.yes") |
||||
record=(alias="no",value="vote.no") |
||||
|
||||
[help CommandAliases] |
||||
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