Anton Tarasenko
2 years ago
15 changed files with 5020 additions and 184 deletions
@ -0,0 +1,35 @@ |
|||||||
|
; Acedia requires adding its own `GameRules` to listen to many different |
||||||
|
; game events. |
||||||
|
|
||||||
|
; In this config you can setup Acedia's user groups and persistent data |
||||||
|
; storage. Enabling this feature automatically enables user group support, |
||||||
|
; while persistent data is optional. |
||||||
|
; Databases can be configured in `AcediaDB.ini`. |
||||||
|
[default Users] |
||||||
|
; Configures whether to use database (and which) for storing user groups. |
||||||
|
; Set `useDatabaseForGroupsData` to `false` if you want to define which users |
||||||
|
; belong to what groups inside this config. |
||||||
|
useDatabaseForGroupsData=true |
||||||
|
groupsDatabaseLink=[local]Database:/group_data |
||||||
|
; Configures whether persistent data should be additionally used. |
||||||
|
; It can only be stored inside a database. |
||||||
|
usePersistentData=true |
||||||
|
persistentDataDatabaseLink=[local]Database:/user_data |
||||||
|
; Available groups. Only used if `useDatabaseForGroupsData` is set to `false`. |
||||||
|
localUserGroup=admin |
||||||
|
localUserGroup=moderator |
||||||
|
localUserGroup=trusted |
||||||
|
|
||||||
|
; These groups definitions only work in case you're using a config with |
||||||
|
; `useDatabaseForGroupsData` set to `false`. Simply add new `user=` record, |
||||||
|
; specifying SteamIDs of the players, e.g. `user=76561197960287930`. |
||||||
|
; You can also optionally specify a human-readable lable for the SteamID after |
||||||
|
; slash "/", e.g. `user=76561197960287930/gabe`. |
||||||
|
[admin UserGroup] |
||||||
|
;user= |
||||||
|
|
||||||
|
[moderator UserGroup] |
||||||
|
;user= |
||||||
|
|
||||||
|
[trusted UserGroup] |
||||||
|
;user= |
@ -0,0 +1,655 @@ |
|||||||
|
/** |
||||||
|
* Command for displaying help information about registered Acedia's commands. |
||||||
|
* Copyright 2022-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 ACommandUserGroups extends Command |
||||||
|
dependson(Users_Feature); |
||||||
|
|
||||||
|
protected function BuildData(CommandDataBuilder builder) |
||||||
|
{ |
||||||
|
builder.Name(P("usergroups")) |
||||||
|
.Group(P("admin")) |
||||||
|
.Summary(P("User groups management.")) |
||||||
|
.Describe(P("Allows to add/remove user groups and users to these:" |
||||||
|
@ "groups. Changes made by it will always affect current session," |
||||||
|
@ "but might fail to be saved in case user groups are stored in" |
||||||
|
@ "a database that is either corrupted or in read-only mode.")); |
||||||
|
builder.SubCommand(P("list")) |
||||||
|
.Describe(P("Lists specified groups along with users that belong to" |
||||||
|
@ "them. If no groups were specified at all - lists all available" |
||||||
|
@ "groups.")) |
||||||
|
.OptionalParams() |
||||||
|
.ParamTextList(P("groups")); |
||||||
|
builder.SubCommand(P("add")) |
||||||
|
.Describe(P("Adds a new group")) |
||||||
|
.ParamText(P("group_name")); |
||||||
|
builder.SubCommand(P("remove")) |
||||||
|
.Describe(P("Removes a group")) |
||||||
|
.ParamText(P("group_name")); |
||||||
|
builder.SubCommand(P("addplayer")) |
||||||
|
.Describe(F("Adds new user to the group, specified by the player" |
||||||
|
@ "selector. Can add several players at once." |
||||||
|
@ "Allows to also optionally specify annotation" |
||||||
|
@ "(human-readable name) that can be thought of as" |
||||||
|
@ "a {$TextEmphasis comment}. If annotation isn't specified" |
||||||
|
@ "current nickname will be used as one.")) |
||||||
|
.ParamText(P("group_name")) |
||||||
|
.ParamPlayers(P("player_selector")) |
||||||
|
.OptionalParams() |
||||||
|
.ParamText(P("annotation")); |
||||||
|
builder.SubCommand(P("removeplayer")) |
||||||
|
.Describe(P("Removes user from the group, specified by player selector." |
||||||
|
@ "Can remove several players at once.")) |
||||||
|
.ParamText(P("group_name")) |
||||||
|
.ParamPlayers(P("player_selector")); |
||||||
|
builder.SubCommand(P("adduser")) |
||||||
|
.Describe(F("Adds new user to the group. Allows to also optionally" |
||||||
|
@ "specify annotation (human-readable name) that can be thought of" |
||||||
|
@ "as a {$TextEmphasis comment}.")) |
||||||
|
.ParamText(P("group_name")) |
||||||
|
.ParamText(P("user_id")) |
||||||
|
.OptionalParams() |
||||||
|
.ParamText(P("annotation")); |
||||||
|
builder.SubCommand(P("removeuser")) |
||||||
|
.Describe(P("Removes user from the group. User can be specified by both" |
||||||
|
@ "user's id or annotation, with id taking priority.")) |
||||||
|
.ParamText(P("group_name")) |
||||||
|
.ParamText(P("user_name")); |
||||||
|
builder.Option(P("force")) |
||||||
|
.Describe(P("Allows to force usage of invalid user IDs.")); |
||||||
|
} |
||||||
|
|
||||||
|
protected function Executed(CallData arguments, EPlayer instigator) |
||||||
|
{ |
||||||
|
local bool forceOption; |
||||||
|
local Text groupName, userID, userName, annotation; |
||||||
|
local ArrayList players, groups; |
||||||
|
|
||||||
|
groupName = arguments.parameters.GetText(P("group_name")); |
||||||
|
// For parameters named `user_id`, can only be ID |
||||||
|
userID = arguments.parameters.GetText(P("user_id")); |
||||||
|
// For parameters named `user_id`, can be either ID or annotation |
||||||
|
userName = arguments.parameters.GetText(P("user_name")); |
||||||
|
annotation = arguments.parameters.GetText(P("annotation")); |
||||||
|
// An array of players that can be specified for some commands |
||||||
|
players = arguments.parameters.GetArrayList(P("player_selector")); |
||||||
|
groups = arguments.parameters.GetArrayList(P("groups")); |
||||||
|
forceOption = arguments.options.HasKey(P("force")); |
||||||
|
if (arguments.subCommandName.IsEmpty()) { |
||||||
|
DisplayUserGroups(); |
||||||
|
} |
||||||
|
else if (arguments.subCommandName.Compare(P("list"), SCASE_SENSITIVE)) { |
||||||
|
DisplayUserGroupsWithUsers(groups); |
||||||
|
} |
||||||
|
else if (arguments.subCommandName.Compare(P("add"), SCASE_SENSITIVE)) { |
||||||
|
AddGroup(groupName); |
||||||
|
} |
||||||
|
else if (arguments.subCommandName.Compare(P("remove"), SCASE_SENSITIVE)) { |
||||||
|
RemoveGroup(groupName); |
||||||
|
} |
||||||
|
else if (arguments.subCommandName.Compare(P("adduser"), SCASE_SENSITIVE)) { |
||||||
|
AddOrAnnotateUser(groupName, userID, annotation, forceOption); |
||||||
|
} |
||||||
|
else if (arguments.subCommandName.Compare(P("removeuser"), SCASE_SENSITIVE)) |
||||||
|
{ |
||||||
|
RemoveUser(groupName, userName); |
||||||
|
} |
||||||
|
else if (arguments.subCommandName.Compare(P("addplayer"), SCASE_SENSITIVE)) { |
||||||
|
AddOrAnnotatePlayers(groupName, players, annotation); |
||||||
|
} |
||||||
|
else if (arguments.subCommandName |
||||||
|
.Compare(P("removeplayer"), SCASE_SENSITIVE)) |
||||||
|
{ |
||||||
|
RemovePlayers(groupName, players); |
||||||
|
} |
||||||
|
_.memory.Free(groupName); |
||||||
|
_.memory.Free(userID); |
||||||
|
_.memory.Free(userName); |
||||||
|
_.memory.Free(annotation); |
||||||
|
_.memory.Free(players); |
||||||
|
_.memory.Free(groups); |
||||||
|
} |
||||||
|
|
||||||
|
private function bool ValidateGroupExistence(BaseText groupName) |
||||||
|
{ |
||||||
|
if (_.users.IsGroupExisting(groupName)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
callerConsole |
||||||
|
.Write(P("Group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.UseColorOnce(_.color.TextFailure) |
||||||
|
.Write(P(" doesn't exists")) |
||||||
|
.WriteLine(P("!")); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private function bool ValidateUserID(BaseText textUserID) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
|
||||||
|
if (textUserID == none) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (textUserID.IsEmpty()) |
||||||
|
{ |
||||||
|
callerConsole.WriteLine(F("Valid User ID" |
||||||
|
@ "{$TextFailure shouldn't be empty}," |
||||||
|
@ "use {$TextEmphasis --force} flag if you want to enforce" |
||||||
|
@ "using it.")); |
||||||
|
return false; |
||||||
|
} |
||||||
|
for (i = 0; i < textUserID.GetLength(); i += 1) |
||||||
|
{ |
||||||
|
if (!_.text.IsDigit(textUserID.GetCharacter(i))) |
||||||
|
{ |
||||||
|
callerConsole.WriteLine(F("Valid User ID" |
||||||
|
@ "{$TextFailure should consist only of digits}," |
||||||
|
@ "use {$TextEmphasis --force} flag if you want" |
||||||
|
@ "to enforce using it.")); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private function bool TryAddingUserID( |
||||||
|
BaseText groupName, |
||||||
|
UserID userID, |
||||||
|
BaseText userSpecifiedID) |
||||||
|
{ |
||||||
|
if (_.users.IsUserIDInGroup(userID, groupName)) |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.Write(P("User id specified as ")) |
||||||
|
.UseColorOnce(_.color.Gray) |
||||||
|
.Write(userSpecifiedID) |
||||||
|
.UseColorOnce(_.color.TextFailure) |
||||||
|
.Write(P(" is already in the group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.WriteLine(P("!")); |
||||||
|
} |
||||||
|
else if (_.users.AddUserIDToGroup(userID, groupName)) |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.Write(F("{$TextPositive Added} user id specified as ")) |
||||||
|
.UseColorOnce(_.color.Gray) |
||||||
|
.Write(userSpecifiedID) |
||||||
|
.Write(P(" to the group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.WriteLine(P("!")); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.UseColorOnce(_.color.TextFailure) |
||||||
|
.Write(P("Failed (for unknown reason)")) |
||||||
|
.Write(P(" to add user id ")) |
||||||
|
.UseColorOnce(_.color.Gray).Write(userSpecifiedID) |
||||||
|
.Write(P(" to the group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis).Write(groupName) |
||||||
|
.WriteLine(P("!")); |
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private function DisplayAnnotation( |
||||||
|
BaseText userSpecifiedName, |
||||||
|
BaseText groupName, |
||||||
|
BaseText annotation) |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.Write(P("Annotation for user id specified as ")) |
||||||
|
.UseColorOnce(_.color.Gray) |
||||||
|
.Write(userSpecifiedName) |
||||||
|
.UseColorOnce(_.color.TextPositive) |
||||||
|
.Write(P(" in the group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.Write(P(" is set to ")) |
||||||
|
.UseColorOnce(_.color.TextNeutral) |
||||||
|
.WriteLine(annotation); |
||||||
|
} |
||||||
|
|
||||||
|
private function AddOrAnnotateUser( |
||||||
|
BaseText groupName, |
||||||
|
BaseText textUserID, |
||||||
|
BaseText annotation, |
||||||
|
bool forceOption) |
||||||
|
{ |
||||||
|
local UserID id; |
||||||
|
|
||||||
|
if (groupName == none) return; |
||||||
|
if (textUserID == none) return; |
||||||
|
if (!ValidateGroupExistence(groupName)) return; |
||||||
|
if (!forceOption && !ValidateUserID(textUserID)) return; |
||||||
|
|
||||||
|
id = UserID(_.memory.Allocate(class'UserID')); |
||||||
|
id.Initialize(textUserID); |
||||||
|
if (!TryAddingUserID(groupName, id, textUserID) || annotation == none) |
||||||
|
{ |
||||||
|
_.memory.Free(id); |
||||||
|
return; |
||||||
|
} |
||||||
|
_.users.SetAnnotationForUserID(groupName, id, annotation); |
||||||
|
_.memory.Free(id); |
||||||
|
DisplayAnnotation(textUserID, groupName, annotation); |
||||||
|
} |
||||||
|
|
||||||
|
private function AddOrAnnotatePlayers( |
||||||
|
BaseText groupName, |
||||||
|
ArrayList players, |
||||||
|
BaseText annotation) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local BaseText playerName, nextAnnotation; |
||||||
|
local EPlayer nextPlayer; |
||||||
|
local UserID nextID; |
||||||
|
|
||||||
|
if (groupName == none) return; |
||||||
|
if (players == none) return; |
||||||
|
if (!ValidateGroupExistence(groupName)) return; |
||||||
|
|
||||||
|
for (i = 0; i < players.GetLength(); i += 1) |
||||||
|
{ |
||||||
|
nextPlayer = EPlayer(players.GetItem(i)); |
||||||
|
if (nextPlayer == none) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
playerName = nextPlayer.GetName(); |
||||||
|
nextID = nextPlayer.GetUserID(); |
||||||
|
if (TryAddingUserID(groupName, nextID, playerName)) |
||||||
|
{ |
||||||
|
if (annotation == none) { |
||||||
|
nextAnnotation = playerName; |
||||||
|
} |
||||||
|
else { |
||||||
|
nextAnnotation = annotation; |
||||||
|
} |
||||||
|
_.users.SetAnnotationForUserID(groupName, nextID, nextAnnotation); |
||||||
|
DisplayAnnotation(playerName, groupName, nextAnnotation); |
||||||
|
_.memory.Free(nextID); |
||||||
|
nextAnnotation = none; |
||||||
|
} |
||||||
|
_.memory.Free(nextPlayer); |
||||||
|
_.memory.Free(playerName); |
||||||
|
_.memory.Free(nextID); |
||||||
|
nextPlayer = none; |
||||||
|
playerName = none; |
||||||
|
nextID = none; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function TryRemovingUserID( |
||||||
|
BaseText groupName, |
||||||
|
UserID idToRemove, |
||||||
|
BaseText userSpecifiedName) |
||||||
|
{ |
||||||
|
local Text idAsText; |
||||||
|
|
||||||
|
idAsText = idToRemove.GetUniqueID(); |
||||||
|
if (_.users.RemoveUserIDFromGroup(idToRemove, groupName)) |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.Write(F("{$TextNegative Removed} user ")) |
||||||
|
.UseColorOnce(_.color.Gray) |
||||||
|
.Write(userSpecifiedName) |
||||||
|
.Write(P(" (with id ")) |
||||||
|
.UseColorOnce(_.color.Gray) |
||||||
|
.Write(idAsText) |
||||||
|
.Write(P(") from the group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.WriteLine(P("!")); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.UseColorOnce(_.color.TextFailure) |
||||||
|
.Write(P("Failed (for unknown reason)")) |
||||||
|
.Write(P("to remove user with id ")) |
||||||
|
.UseColorOnce(_.color.Gray) |
||||||
|
.Write(idAsText) |
||||||
|
.Write(P(" from the group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.WriteLine(P(".")); |
||||||
|
} |
||||||
|
_.memory.Free(idAsText); |
||||||
|
} |
||||||
|
|
||||||
|
private function bool RemoveUsersByAnnotation( |
||||||
|
BaseText groupName, |
||||||
|
BaseText userName) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool removedUser; |
||||||
|
local array<Users_Feature.AnnotatedUserID> annotatedUsers; |
||||||
|
|
||||||
|
annotatedUsers = _.users.GetAnnotatedGroupMembers(groupName); |
||||||
|
for (i = 0; i < annotatedUsers.length; i += 1) |
||||||
|
{ |
||||||
|
if (userName.Compare(annotatedUsers[i].annotation, SCASE_INSENSITIVE)) |
||||||
|
{ |
||||||
|
TryRemovingUserID(groupName, annotatedUsers[i].id, userName); |
||||||
|
removedUser = true; |
||||||
|
} |
||||||
|
} |
||||||
|
for (i = 0; i < annotatedUsers.length; i += 1) |
||||||
|
{ |
||||||
|
_.memory.Free(annotatedUsers[i].id); |
||||||
|
_.memory.Free(annotatedUsers[i].annotation); |
||||||
|
} |
||||||
|
return removedUser; |
||||||
|
} |
||||||
|
|
||||||
|
private function RemoveUser(BaseText groupName, BaseText userName) |
||||||
|
{ |
||||||
|
local bool matchedUserName; |
||||||
|
local UserID idFromName; |
||||||
|
|
||||||
|
if (groupName == none) return; |
||||||
|
if (userName == none) return; |
||||||
|
if (!ValidateGroupExistence(groupName)) return; |
||||||
|
|
||||||
|
idFromName = UserID(_.memory.Allocate(class'UserID')); |
||||||
|
idFromName.Initialize(userName); |
||||||
|
if ( idFromName.IsInitialized() |
||||||
|
&& _.users.IsUserIDInGroup(idFromName, groupName)) |
||||||
|
{ |
||||||
|
TryRemovingUserID(groupName, idFromName, userName); |
||||||
|
matchedUserName = true; |
||||||
|
} |
||||||
|
else { |
||||||
|
matchedUserName = RemoveUsersByAnnotation(groupName, userName); |
||||||
|
} |
||||||
|
_.memory.Free(idFromName); |
||||||
|
if (!matchedUserName) |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.Write(P("User ")) |
||||||
|
.UseColorOnce(_.color.Gray) |
||||||
|
.Write(userName) |
||||||
|
.UseColorOnce(_.color.TextFailure) |
||||||
|
.Write(P(" doesn't belong to the group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.WriteLine(P("!")); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function RemovePlayers(BaseText groupName, ArrayList players) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local Text playerName; |
||||||
|
local EPlayer nextPlayer; |
||||||
|
local UserID nextID; |
||||||
|
|
||||||
|
if (groupName == none) return; |
||||||
|
if (players == none) return; |
||||||
|
if (!ValidateGroupExistence(groupName)) return; |
||||||
|
|
||||||
|
for (i = 0; i < players.GetLength(); i += 1) |
||||||
|
{ |
||||||
|
nextPlayer = EPlayer(players.GetItem(i)); |
||||||
|
if (nextPlayer == none) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
playerName = nextPlayer.GetName(); |
||||||
|
nextID = nextPlayer.GetUserID(); |
||||||
|
if (!_.users.IsUserIDInGroup(nextID, groupName)) |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.Write(P("Player ")) |
||||||
|
.UseColorOnce(_.color.Gray) |
||||||
|
.Write(playerName) |
||||||
|
.Write(F(" {$TextFailure doesn't belong} to the group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.WriteLine(P("!")); |
||||||
|
} |
||||||
|
else { |
||||||
|
TryRemovingUserID(groupName, nextID, playerName); |
||||||
|
} |
||||||
|
_.memory.Free(nextPlayer); |
||||||
|
_.memory.Free(playerName); |
||||||
|
_.memory.Free(nextID); |
||||||
|
nextPlayer = none; |
||||||
|
playerName = none; |
||||||
|
nextID = none; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function AddGroup(BaseText groupName) |
||||||
|
{ |
||||||
|
if (_.users.IsGroupExisting(groupName)) |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.Write(P("Group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.UseColorOnce(_.color.TextNegative) |
||||||
|
.Write(P(" already exists")) |
||||||
|
.WriteLine(P("!")); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (_.users.AddGroup(groupName)) |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.Write(P("Group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.UseColorOnce(_.color.TextPositive) |
||||||
|
.Write(P(" was added")) |
||||||
|
.WriteLine(P("!")); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.UseColorOnce(_.color.TextFailure) |
||||||
|
.Write(P("Cannot add")) |
||||||
|
.Write(P(" group with a name ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.WriteLine(P(" for unknown reason.")); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function RemoveGroup(BaseText groupName) |
||||||
|
{ |
||||||
|
if (!_.users.IsGroupExisting(groupName)) |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.Write(P("Group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.UseColorOnce(_.color.TextNegative) |
||||||
|
.Write(P(" doesn't exists")) |
||||||
|
.WriteLine(P("!")); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (_.users.RemoveGroup(groupName)) |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.Write(P("Group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.UseColorOnce(_.color.TextPositive) |
||||||
|
.Write(P(" was removed")) |
||||||
|
.WriteLine(P("!")); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.UseColorOnce(_.color.TextFailure) |
||||||
|
.Write(P("Cannot remove")) |
||||||
|
.Write(P(" group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(groupName) |
||||||
|
.WriteLine(P(" for unknown reason.")); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function DisplayUserGroups() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<Text> availableGroups; |
||||||
|
|
||||||
|
if (!ValidateUsersFeature()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
availableGroups = _.users.GetAvailableGroups(); |
||||||
|
if (availableGroups.length <= 0) |
||||||
|
{ |
||||||
|
callerConsole.WriteLine(F("{$TextNegative No user groups}" |
||||||
|
@ "currently available.")); |
||||||
|
return; |
||||||
|
} |
||||||
|
callerConsole |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(P("Available user groups")) |
||||||
|
.Write(P(": ")); |
||||||
|
for (i = 0; i < availableGroups.length; i += 1) |
||||||
|
{ |
||||||
|
if (i > 0) { |
||||||
|
callerConsole.Write(P(", ")); |
||||||
|
} |
||||||
|
callerConsole.Write(availableGroups[i]); |
||||||
|
} |
||||||
|
callerConsole.Flush(); |
||||||
|
_.memory.FreeMany(availableGroups); |
||||||
|
} |
||||||
|
|
||||||
|
private function bool ValidateUsersFeature() |
||||||
|
{ |
||||||
|
if (class'Users_Feature'.static.IsEnabled()) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
callerConsole |
||||||
|
.UseColorOnce(_.color.TextFailure) |
||||||
|
.WriteLine(P("`Users_Feature` is currently disabled.")); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private function bool IsGroupSpecified( |
||||||
|
ArrayList specifiedGroups, |
||||||
|
BaseText groupToCheck) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int length; |
||||||
|
local Text nextGroup; |
||||||
|
|
||||||
|
if (groupToCheck == none) return false; |
||||||
|
if (specifiedGroups == none) return true; |
||||||
|
length = groupToCheck.GetLength(); |
||||||
|
if (length <= 0) return true; |
||||||
|
|
||||||
|
for (i = 0; i < length; i += 1) |
||||||
|
{ |
||||||
|
nextGroup = specifiedGroups.GetText(i); |
||||||
|
if (groupToCheck.Compare(nextGroup, SCASE_INSENSITIVE)) |
||||||
|
{ |
||||||
|
nextGroup.FreeSelf(); |
||||||
|
return true; |
||||||
|
} |
||||||
|
_.memory.Free(nextGroup); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private function DisplayUserGroupsWithUsers(ArrayList specifiedGroups) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool displayedGroup; |
||||||
|
local array<Text> availableGroups; |
||||||
|
|
||||||
|
if (!ValidateUsersFeature()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
availableGroups = _.users.GetAvailableGroups(); |
||||||
|
if (availableGroups.length <= 0) |
||||||
|
{ |
||||||
|
callerConsole.WriteLine(F("{$TextNegative No user groups}" |
||||||
|
@ "currently available.")); |
||||||
|
return; |
||||||
|
} |
||||||
|
for (i = 0; i < availableGroups.length; i += 1) |
||||||
|
{ |
||||||
|
if (IsGroupSpecified(specifiedGroups, availableGroups[i])) |
||||||
|
{ |
||||||
|
displayedGroup = true; |
||||||
|
callerConsole |
||||||
|
.Write(P("User group ")) |
||||||
|
.UseColorOnce(_.color.TextEmphasis) |
||||||
|
.Write(availableGroups[i]) |
||||||
|
.WriteLine(P(":")); |
||||||
|
DisplayUsersFor(availableGroups[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
callerConsole.Flush(); |
||||||
|
_.memory.FreeMany(availableGroups); |
||||||
|
if (!displayedGroup && specifiedGroups != none) { |
||||||
|
callerConsole.WriteLine(F("{$TextFailure No valid groups} specified!")); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function DisplayUsersFor(Text groupName) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local Text nextID; |
||||||
|
local array<Users_Feature.AnnotatedUserID> annotatedUsers; |
||||||
|
|
||||||
|
annotatedUsers = _.users.GetAnnotatedGroupMembers(groupName); |
||||||
|
if (annotatedUsers.length <= 0) |
||||||
|
{ |
||||||
|
callerConsole.WriteBlock(P("No users")); |
||||||
|
return; |
||||||
|
} |
||||||
|
for (i = 0; i < annotatedUsers.length; i += 1) |
||||||
|
{ |
||||||
|
if (annotatedUsers[i].id == none) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
nextID = annotatedUsers[i].id.GetUniqueID(); |
||||||
|
if (annotatedUsers[i].annotation != none) |
||||||
|
{ |
||||||
|
callerConsole |
||||||
|
.Write(nextID) |
||||||
|
.UseColorOnce(_.color.TextNeutral) |
||||||
|
.Write(P(" aka ")) |
||||||
|
.WriteBlock(annotatedUsers[i].annotation); |
||||||
|
} |
||||||
|
else { |
||||||
|
callerConsole.WriteBlock(nextID); |
||||||
|
} |
||||||
|
_.memory.Free(nextID); |
||||||
|
} |
||||||
|
for (i = 0; i < annotatedUsers.length; i += 1) |
||||||
|
{ |
||||||
|
_.memory.Free(annotatedUsers[i].id); |
||||||
|
_.memory.Free(annotatedUsers[i].annotation); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
/** |
||||||
|
* Signal class for `PersistentDataManager`'s `OnPersistentDataReady()` signal. |
||||||
|
* 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 PersistentDataManager_OnPersistentDataReady_Signal extends Signal |
||||||
|
dependson(DBConnection); |
||||||
|
|
||||||
|
public final function Emit(UserID id, bool online) |
||||||
|
{ |
||||||
|
local Slot nextSlot; |
||||||
|
StartIterating(); |
||||||
|
nextSlot = GetNextSlot(); |
||||||
|
while (nextSlot != none) |
||||||
|
{ |
||||||
|
PersistentDataManager_OnPersistentDataReady_Slot(nextSlot) |
||||||
|
.connect(id, online); |
||||||
|
nextSlot = GetNextSlot(); |
||||||
|
} |
||||||
|
CleanEmptySlots(); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedSlotClass = class'PersistentDataManager_OnPersistentDataReady_Slot' |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
/** |
||||||
|
* Slot class for `PersistentDataManager`'s `OnPersistentDataReady()` signal. |
||||||
|
* 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 PersistentDataManager_OnPersistentDataReady_Slot extends Slot |
||||||
|
dependson(DBConnection); |
||||||
|
|
||||||
|
delegate connect(UserID id, bool online) |
||||||
|
{ |
||||||
|
DummyCall(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function Constructor() |
||||||
|
{ |
||||||
|
connect = none; |
||||||
|
} |
||||||
|
|
||||||
|
protected function Finalizer() |
||||||
|
{ |
||||||
|
super.Finalizer(); |
||||||
|
connect = none; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,407 @@ |
|||||||
|
/** |
||||||
|
* This tool is for simplifying writing and reading persistent user data. |
||||||
|
* All it requires is a setup of database + json pointer to data and it will |
||||||
|
* take care of data caching and database connection. |
||||||
|
* 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 PersistentDataManager extends AcediaObject; |
||||||
|
|
||||||
|
/** |
||||||
|
* # `PersistentDataManager` |
||||||
|
* |
||||||
|
* This tool is for simplifying writing and reading persistent user data. |
||||||
|
* All it requires is a setup of database + json pointer to data and it will |
||||||
|
* take care of data caching and database connection. |
||||||
|
* |
||||||
|
* ## Usage |
||||||
|
* |
||||||
|
* Create an instance and use `Setup()` to connect to the database with |
||||||
|
* persistent data. You can use `Setup()` again on the same object to setup |
||||||
|
* a different database as a source. All data will be automatically reloaded. |
||||||
|
* After that you can use `GetPersistentData()`/`SetPersistentData()` to |
||||||
|
* read/write persistent data for the particular user. |
||||||
|
* Since loading data from the database takes time, you don't have an |
||||||
|
* immediate access to it. |
||||||
|
* But you can use `_.users.OnPersistentDataAvailable()` signal to track |
||||||
|
* whenever new user data from database becomes available. However, you can |
||||||
|
* start writing persistent data (and reading what you've wrote) at any time it |
||||||
|
* - these changes will be reapplied whenever data is actually loaded from |
||||||
|
* database. |
||||||
|
* |
||||||
|
* ## Implementation |
||||||
|
* |
||||||
|
* Implementation consists of simply creating `DBConnection` for every user |
||||||
|
* and storing them in the `HashTable` that maps user IDs into those |
||||||
|
* `DBConnection`s. |
||||||
|
* We also maintain a reverse map to figure out what `DBConnection` belongs |
||||||
|
* to what user when connection signals an update. We borrow the signal that |
||||||
|
* `UsersAPI` provides to inform everyone interested about which users |
||||||
|
* have updated. |
||||||
|
*/ |
||||||
|
|
||||||
|
var private bool initialized; |
||||||
|
var private Database database; |
||||||
|
var private JSONPointer rootPointer; |
||||||
|
var private HashTable userToConnection, connectionToUser; |
||||||
|
|
||||||
|
var private PersistentDataManager_OnPersistentDataReady_Signal onPersistentDataReadySignal; |
||||||
|
|
||||||
|
protected function Constructor() |
||||||
|
{ |
||||||
|
_.players.OnNewPlayer(self).connect = ConnectPersistentData; |
||||||
|
onPersistentDataReadySignal = _.users._getOnReadySignal(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function Finalizer() |
||||||
|
{ |
||||||
|
Reset(); |
||||||
|
_.players.OnNewPlayer(self).Disconnect(); |
||||||
|
} |
||||||
|
|
||||||
|
private final function Reset() |
||||||
|
{ |
||||||
|
_.memory.Free(database); |
||||||
|
_.memory.Free(rootPointer); |
||||||
|
_.memory.Free(userToConnection); |
||||||
|
_.memory.Free(connectionToUser); |
||||||
|
_.memory.Free(onPersistentDataReadySignal); |
||||||
|
database = none; |
||||||
|
rootPointer = none; |
||||||
|
userToConnection = none; |
||||||
|
connectionToUser = none; |
||||||
|
onPersistentDataReadySignal = none; |
||||||
|
initialized = false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets up database and location inside it as a source of users' persistent |
||||||
|
* data. |
||||||
|
* |
||||||
|
* Must be successfully called at least once for the caller |
||||||
|
* `PersistentDataManager` to be usable. |
||||||
|
* |
||||||
|
* @param db Database inside which persistent data is stored. |
||||||
|
* @param location Location inside specified database to the root of |
||||||
|
* persistent data. |
||||||
|
* @return `true` if setup was successful (requires both arguments to be not |
||||||
|
* `none`) and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool Setup(Database db, JSONPointer location) |
||||||
|
{ |
||||||
|
if (db == none) return false; |
||||||
|
if (location == none) return false; |
||||||
|
|
||||||
|
Reset(); |
||||||
|
database = db; |
||||||
|
database.NewRef(); |
||||||
|
rootPointer = location.Copy(); |
||||||
|
userToConnection = _.collections.EmptyHashTable(); |
||||||
|
connectionToUser = _.collections.EmptyHashTable(); |
||||||
|
// Using `userToConnection` as an empty hash table, not related to its |
||||||
|
// actual meaning |
||||||
|
database.IncrementData(location, userToConnection); |
||||||
|
initialized = true; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Reads specified named persistent data for the specified group. |
||||||
|
* |
||||||
|
* @param id ID of the user to read persistent data from. |
||||||
|
* @param groupName Group to which this persistent data belongs to. |
||||||
|
* Groups are used as namespaces to avoid duplicate persistent variables |
||||||
|
* between mods. If your mod needs several subgroups, its recommended to |
||||||
|
* use the same prefix for them, e.g. "MyAwesomeMod.economy" and |
||||||
|
* "MyAwesomeMod.enemies". |
||||||
|
* @param dataName Name of persistent data variable to read inside |
||||||
|
* `groupName` persistent data group. Not `none` value must be provided. |
||||||
|
* @param data Data to set as persistent value. Must be |
||||||
|
* JSON-compatible. If `none` is passed, returns the all data for |
||||||
|
* the given group. |
||||||
|
* @return Data read from the persistent variable. `none` in case of any kind |
||||||
|
* of failure. |
||||||
|
*/ |
||||||
|
public final function AcediaObject GetPersistentData( |
||||||
|
UserID id, |
||||||
|
BaseText groupName, |
||||||
|
optional BaseText dataName) |
||||||
|
{ |
||||||
|
local AcediaObject result; |
||||||
|
local Text textID; |
||||||
|
local JSONPointer location; |
||||||
|
local DBConnection relevantConnection; |
||||||
|
|
||||||
|
if (!initialized) return none; |
||||||
|
if (id == none) return none; |
||||||
|
if (groupName == none) return none; |
||||||
|
|
||||||
|
textID = id.GetUniqueID(); |
||||||
|
relevantConnection = DBConnection(userToConnection.GetItem(textID)); |
||||||
|
textID.FreeSelf(); |
||||||
|
if (relevantConnection != none) |
||||||
|
{ |
||||||
|
location = _.json.Pointer(); |
||||||
|
location.Push(groupName); |
||||||
|
if (dataName != none) { |
||||||
|
location.Push(dataName); |
||||||
|
} |
||||||
|
result = relevantConnection.ReadDataByJSON(location); |
||||||
|
relevantConnection.FreeSelf(); |
||||||
|
location.FreeSelf(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Writes specified named persistent data for the specified group. |
||||||
|
* |
||||||
|
* @param id ID of the user to change persistent data of. |
||||||
|
* @param groupName Group to which this persistent data belongs to. |
||||||
|
* Groups are used as namespaces to avoid duplicate persistent variables |
||||||
|
* between mods. If your mod needs several subgroups, its recommended to |
||||||
|
* use the same prefix for them, e.g. "MyAwesomeMod.economy" and |
||||||
|
* "MyAwesomeMod.enemies". |
||||||
|
* @param dataName Name of persistent data variable to change inside |
||||||
|
* `groupName` persistent data group. |
||||||
|
* @param data Data to set as persistent value. Must be |
||||||
|
* JSON-compatible. |
||||||
|
* @return `true` if change succeeded in local cached version of database with |
||||||
|
* persistent values and `false` otherwise. Such local changes can |
||||||
|
* potentially be not applied to the actual database. But successful local |
||||||
|
* changes should persist for the game session. |
||||||
|
*/ |
||||||
|
public final function bool WritePersistentData( |
||||||
|
UserID id, |
||||||
|
BaseText groupName, |
||||||
|
BaseText dataName, |
||||||
|
AcediaObject data) |
||||||
|
{ |
||||||
|
local bool result; |
||||||
|
local Text textID; |
||||||
|
local JSONPointer location; |
||||||
|
local DBConnection relevantConnection; |
||||||
|
local HashTable emptyObject; |
||||||
|
|
||||||
|
if (!initialized) return false; |
||||||
|
if (id == none) return false; |
||||||
|
if (groupName == none) return false; |
||||||
|
if (dataName == none) return false; |
||||||
|
|
||||||
|
textID = id.GetUniqueID(); |
||||||
|
relevantConnection = DBConnection(userToConnection.GetItem(textID)); |
||||||
|
textID.FreeSelf(); |
||||||
|
if (relevantConnection != none) |
||||||
|
{ |
||||||
|
emptyObject = _.collections.EmptyHashTable(); |
||||||
|
location = _.json.Pointer(); |
||||||
|
location.Push(groupName); |
||||||
|
relevantConnection.IncrementDataByJSON(location, emptyObject); |
||||||
|
location.Push(dataName); |
||||||
|
result = relevantConnection.WriteDataByJSON(location, data); |
||||||
|
relevantConnection.FreeSelf(); |
||||||
|
location.FreeSelf(); |
||||||
|
emptyObject.FreeSelf(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Increments specified named persistent data for the specified group. |
||||||
|
* |
||||||
|
* @param id ID of the user to change persistent data of. |
||||||
|
* @param groupName Group to which this persistent data belongs to. |
||||||
|
* Groups are used as namespaces to avoid duplicate persistent variables |
||||||
|
* between mods. If your mod needs several subgroups, its recommended to |
||||||
|
* use the same prefix for them, e.g. "MyAwesomeMod.economy" and |
||||||
|
* "MyAwesomeMod.enemies". |
||||||
|
* @param dataName Name of persistent data variable to change inside |
||||||
|
* `groupName` persistent data group. |
||||||
|
* @param data Data by which to increment existing persistent value. |
||||||
|
* Must be JSON-compatible. |
||||||
|
* @return `true` if change succeeded in local cached version of database with |
||||||
|
* persistent values and `false` otherwise. Such local changes can |
||||||
|
* potentially be not applied to the actual database. But successful local |
||||||
|
* changes should persist for the game session. |
||||||
|
*/ |
||||||
|
public final function bool IncrementPersistentData( |
||||||
|
UserID id, |
||||||
|
BaseText groupName, |
||||||
|
BaseText dataName, |
||||||
|
AcediaObject data) |
||||||
|
{ |
||||||
|
local bool result; |
||||||
|
local Text textID; |
||||||
|
local JSONPointer location; |
||||||
|
local DBConnection relevantConnection; |
||||||
|
|
||||||
|
if (!initialized) return false; |
||||||
|
if (id == none) return false; |
||||||
|
if (groupName == none) return false; |
||||||
|
if (dataName == none) return false; |
||||||
|
|
||||||
|
textID = id.GetUniqueID(); |
||||||
|
relevantConnection = DBConnection(userToConnection.GetItem(textID)); |
||||||
|
textID.FreeSelf(); |
||||||
|
if (relevantConnection != none) |
||||||
|
{ |
||||||
|
location = _.json.Pointer(); |
||||||
|
location.Push(groupName).Push(dataName); |
||||||
|
result = relevantConnection.IncrementDataByJSON(location, data); |
||||||
|
relevantConnection.FreeSelf(); |
||||||
|
location.FreeSelf(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes specified named persistent data for the specified group. |
||||||
|
* |
||||||
|
* @param id ID of the user to remove persistent data of. |
||||||
|
* @param groupName Group to which this persistent data belongs to. |
||||||
|
* Groups are used as namespaces to avoid duplicate persistent variables |
||||||
|
* between mods. If your mod needs several subgroups, its recommended to |
||||||
|
* use the same prefix for them, e.g. "MyAwesomeMod.economy" and |
||||||
|
* "MyAwesomeMod.enemies". |
||||||
|
* @param dataName Name of persistent data variable to remove inside |
||||||
|
* `groupName` persistent data group. |
||||||
|
* @return `true` if removal succeeded in local cached version of database with |
||||||
|
* persistent values and `false` otherwise. Such local changes can |
||||||
|
* potentially be not applied to the actual database. But successful local |
||||||
|
* changes should persist for the game session. |
||||||
|
*/ |
||||||
|
public final function bool RemovePersistentData( |
||||||
|
UserID id, |
||||||
|
BaseText groupName, |
||||||
|
BaseText dataName) |
||||||
|
{ |
||||||
|
local bool result; |
||||||
|
local Text textID; |
||||||
|
local JSONPointer location; |
||||||
|
local DBConnection relevantConnection; |
||||||
|
|
||||||
|
if (!initialized) return false; |
||||||
|
if (id == none) return false; |
||||||
|
if (groupName == none) return false; |
||||||
|
if (dataName == none) return false; |
||||||
|
|
||||||
|
textID = id.GetUniqueID(); |
||||||
|
relevantConnection = DBConnection(userToConnection.GetItem(textID)); |
||||||
|
textID.FreeSelf(); |
||||||
|
if (relevantConnection != none) |
||||||
|
{ |
||||||
|
location = _.json.Pointer(); |
||||||
|
location.Push(groupName).Push(dataName); |
||||||
|
result = relevantConnection.RemoveDataByJSON(location); |
||||||
|
relevantConnection.FreeSelf(); |
||||||
|
location.FreeSelf(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Connects and starts synchronizing persistent data for the given player. |
||||||
|
* |
||||||
|
* @param player Player to synchronize persistent data for. |
||||||
|
*/ |
||||||
|
public final function ConnectPersistentData(EPlayer player) |
||||||
|
{ |
||||||
|
local UserID playerID; |
||||||
|
|
||||||
|
if (initialized && player != none) |
||||||
|
{ |
||||||
|
playerID = player.GetUserID(); |
||||||
|
ConnectPersistentDataByID(playerID); |
||||||
|
_.memory.Free(playerID); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Connects and starts synchronizing persistent data for the player given by |
||||||
|
* their ID. |
||||||
|
* |
||||||
|
* @param id User ID for which to synchronize persistent data from |
||||||
|
* the database. |
||||||
|
*/ |
||||||
|
public final function ConnectPersistentDataByID(UserID id) |
||||||
|
{ |
||||||
|
local Text textID; |
||||||
|
local DBConnection newConnection; |
||||||
|
|
||||||
|
if (!initialized) return; |
||||||
|
if (id == none) return; |
||||||
|
|
||||||
|
textID = id.GetUniqueID(); |
||||||
|
if (userToConnection.HasKey(textID)) |
||||||
|
{ |
||||||
|
_.memory.Free(textID); |
||||||
|
return; |
||||||
|
} |
||||||
|
rootPointer.Push(textID); |
||||||
|
newConnection = DBConnection(_.memory.Allocate(class'DBConnection')); |
||||||
|
newConnection.Initialize(database, rootPointer); |
||||||
|
_.memory.Free(rootPointer.Pop()); |
||||||
|
newConnection.Connect(); |
||||||
|
userToConnection.SetItem(textID, newConnection); |
||||||
|
connectionToUser.SetItem(newConnection, textID); |
||||||
|
newConnection.OnStateChanged(self).connect = UserUpdated; |
||||||
|
textID.FreeSelf(); |
||||||
|
newConnection.FreeSelf(); |
||||||
|
} |
||||||
|
|
||||||
|
private final function UserUpdated( |
||||||
|
DBConnection instance, |
||||||
|
DBConnection.DBConnectionState oldState, |
||||||
|
DBConnection.DBConnectionState newState) |
||||||
|
{ |
||||||
|
local UserID id; |
||||||
|
|
||||||
|
if (!initialized) return; |
||||||
|
if (newState == DBCS_Connecting) return; |
||||||
|
if (onPersistentDataReadySignal == none) return; |
||||||
|
if (!onPersistentDataReadySignal.IsAllocated()) return; |
||||||
|
|
||||||
|
id = UserID(connectionToUser.GetItem(instance)); |
||||||
|
if (id != none) |
||||||
|
{ |
||||||
|
onPersistentDataReadySignal.Emit(id, newState == DBCS_Connected); |
||||||
|
id.FreeSelf(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Attempts to start persistent data synchronization for all players currently |
||||||
|
* on the server. |
||||||
|
*/ |
||||||
|
public final function LoadCurrentPlayers() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<EPlayer> currentPlayers; |
||||||
|
|
||||||
|
if (initialized) |
||||||
|
{ |
||||||
|
currentPlayers = _.players.GetAll(); |
||||||
|
for (i = 0; i < currentPlayers.length; i += 1) { |
||||||
|
ConnectPersistentData(currentPlayers[i]); |
||||||
|
} |
||||||
|
_.memory.FreeMany(currentPlayers); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
/** |
||||||
|
* Acedia's class for defining user group in config files. |
||||||
|
* Copyright 2022 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 UserGroup extends AcediaConfig |
||||||
|
perobjectconfig |
||||||
|
config(AcediaUsers); |
||||||
|
|
||||||
|
var public config array<string> user; |
||||||
|
|
||||||
|
protected function HashTable ToData() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local HashTable data; |
||||||
|
local ArrayList wrappedUserArray; |
||||||
|
|
||||||
|
data = __().collections.EmptyHashTable(); |
||||||
|
wrappedUserArray = __().collections.EmptyArrayList(); |
||||||
|
for (i = 0; i < user.length; i += 1) { |
||||||
|
wrappedUserArray.AddString(user[i]); |
||||||
|
} |
||||||
|
data.SetItem(P("user"), wrappedUserArray); |
||||||
|
wrappedUserArray.FreeSelf(); |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
protected function FromData(HashTable source) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local ArrayList wrappedUserArray; |
||||||
|
|
||||||
|
DefaultIt(); |
||||||
|
if (source == none) { |
||||||
|
return; |
||||||
|
} |
||||||
|
wrappedUserArray = source.GetArrayList(P("user")); |
||||||
|
if (wrappedUserArray == none) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for (i = 0; i < wrappedUserArray.GetLength(); i += 1) { |
||||||
|
user[user.length] = wrappedUserArray.GetString(i); |
||||||
|
} |
||||||
|
wrappedUserArray.FreeSelf(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function DefaultIt() |
||||||
|
{ |
||||||
|
user.length = 0; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
configName = "AcediaUsers" |
||||||
|
supportsDataConversion = true |
||||||
|
} |
@ -0,0 +1,99 @@ |
|||||||
|
/** |
||||||
|
* Config object for `Users_Feature`. |
||||||
|
* Copyright 2022 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 Users extends FeatureConfig |
||||||
|
perobjectconfig |
||||||
|
config(AcediaUsers); |
||||||
|
|
||||||
|
var public config bool usePersistentData; |
||||||
|
var public config string persistentDataDatabaseLink; |
||||||
|
var public config bool useDatabaseForGroupsData; |
||||||
|
var public config string groupsDatabaseLink; |
||||||
|
var public config array<string> localUserGroup; |
||||||
|
|
||||||
|
protected function HashTable ToData() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local HashTable data; |
||||||
|
local ArrayList userGroupList; |
||||||
|
|
||||||
|
data = __().collections.EmptyHashTable(); |
||||||
|
data.SetBool(P("usePersistentData"), usePersistentData); |
||||||
|
data.SetString(P("persistentDataDatabaseLink"), persistentDataDatabaseLink); |
||||||
|
data.SetBool(P("useDatabaseForGroupsData"), useDatabaseForGroupsData); |
||||||
|
data.SetString(P("groupsDatabaseLink"), groupsDatabaseLink); |
||||||
|
userGroupList = _.collections.EmptyArrayList(); |
||||||
|
for (i = 0; i < localUserGroup.length; i += 1) { |
||||||
|
userGroupList.AddString(localUserGroup[i]); |
||||||
|
} |
||||||
|
data.SetItem(P("userGroups"), userGroupList); |
||||||
|
userGroupList.FreeSelf(); |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
protected function FromData(HashTable source) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local ArrayList userGroupList; |
||||||
|
|
||||||
|
if (source == none) { |
||||||
|
return; |
||||||
|
} |
||||||
|
usePersistentData = source.GetBool(P("usePersistentData")); |
||||||
|
persistentDataDatabaseLink = source.GetString( |
||||||
|
P("persistentDataDatabaseLink"), |
||||||
|
"[local]database:/persistent_data"); |
||||||
|
useDatabaseForGroupsData = source.GetBool(P("useDatabaseForGroupsData")); |
||||||
|
groupsDatabaseLink = source.GetString( |
||||||
|
P("groupsDatabaseLink"), |
||||||
|
"[local]database:/groups_data"); |
||||||
|
userGroupList = source.GetArrayList(P("userGroups")); |
||||||
|
localUserGroup.length = 0; |
||||||
|
if (userGroupList == none) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for (i = 0; i < userGroupList.GetLength(); i += 1) { |
||||||
|
localUserGroup[localUserGroup.length] = userGroupList.GetString(i); |
||||||
|
} |
||||||
|
userGroupList.FreeSelf(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function DefaultIt() |
||||||
|
{ |
||||||
|
usePersistentData = false; |
||||||
|
persistentDataDatabaseLink = "[local]database:/persistent_data"; |
||||||
|
useDatabaseForGroupsData = false; |
||||||
|
groupsDatabaseLink = "[local]database:/groups_data"; |
||||||
|
localUserGroup.length = 0; |
||||||
|
localUserGroup[0] = "admin"; |
||||||
|
localUserGroup[1] = "moderator"; |
||||||
|
localUserGroup[2] = "trusted"; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
configName = "AcediaUsers" |
||||||
|
usePersistentData = false |
||||||
|
persistentDataDatabaseLink = "[local]database:/persistent_data" |
||||||
|
useDatabaseForGroupsData = false |
||||||
|
groupsDatabaseLink = "[local]database:/groups_data" |
||||||
|
localUserGroup(0) = "admin" |
||||||
|
localUserGroup(1) = "moderator" |
||||||
|
localUserGroup(2) = "trusted" |
||||||
|
} |
Loading…
Reference in new issue