/** * API that allows easy access to `User` persistent data and `UserID`s. * Copyright 2020-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 . */ class UserAPI extends AcediaObject dependson(Users_Feature) config(AcediaSystem); var private config string userdataDBLink; // Active `Users_Feature`, remember it along with life version to avoid // taking up a reference var private int usersFeatureLifeVersion; var private Users_Feature usersFeature; // Database where user's data (persistent data and user groups) is stored var private Database persistentDatabase; var private LoggerAPI.Definition warnNoPersistentDatabaseLink; var private LoggerAPI.Definition errNoPersistentDatabase; var private LoggerAPI.Definition errCannotCreateSkeletonFor; var private LoggerAPI.Definition infoPersistentDatabaseLoaded; protected function Constructor() { SetupUserDataDatabase(); } // DO NOT CALL MANUALLY public function _reloadFeature() { if ( usersFeature != none && usersFeature.GetLifeVersion() == usersFeatureLifeVersion) { usersFeature.FreeSelf(); usersFeature = none; } usersFeature = Users_Feature(class'Users_Feature'.static.GetEnabledInstance()); if (usersFeature != none) { usersFeatureLifeVersion = usersFeature.GetLifeVersion(); } _.memory.Free(usersFeature); } // Loads persistent user database, specified by the AcediaCore's config and // creates a basic skeleton for storing its data private function SetupUserDataDatabase() { local Text persistentDataLink; local JSONPointer persistentDataPointer; local HashTable skeleton, emptyObject; if (persistentDatabase != none) { return; } // Check if database was even specified persistentDataLink = GetPersistentDataLink(); if (persistentDataLink.IsEmpty()) { _.logger.Auto(warnNoPersistentDatabaseLink); persistentDataLink.FreeSelf(); return; } // If link was specified - try loading database from it persistentDatabase = _.db.Load(persistentDataLink); if (persistentDatabase == none) { _.logger.Auto(errNoPersistentDatabase).Arg(persistentDataLink); persistentDataLink.FreeSelf(); return; } // Write skeleton database's skeleton skeleton = _.collections.EmptyHashTable(); emptyObject = _.collections.EmptyHashTable(); skeleton.SetItem(P("Groups"), emptyObject); skeleton.SetItem(P("PerUserData"), emptyObject); persistentDataPointer = _.db.GetPointer(persistentDataLink); persistentDatabase .IncrementData(persistentDataPointer, skeleton) .connect = ReportSkeletonCreationResult; skeleton.FreeSelf(); emptyObject.FreeSelf(); persistentDataLink.FreeSelf(); _.memory.Free(persistentDataPointer); } private function ReportSkeletonCreationResult( Database.DBQueryResult result, Database source) { local Text persistentDataLink; persistentDataLink = GetPersistentDataLink(); if (result == DBR_Success) { _.logger.Auto(infoPersistentDatabaseLoaded).Arg(persistentDataLink); } else { _.logger.Auto(errCannotCreateSkeletonFor).Arg(persistentDataLink); _.memory.Free(persistentDatabase); persistentDatabase = none; } _.memory.Free(persistentDataLink); } /** * Returns reference to the database of user records that Acedia was * set up to use. * * `UserDatabase` is for storing a set of users that joined the game during * the session, for database that stores persistent user data * @see `GetPersistentDatabase()`. * * @return Main `UserDatabase` that Acedia currently uses to load and * store user information. Guaranteed to be a valid non-`none` reference. */ public final function UserDatabase GetDatabase() { return class'UserDatabase'.static.GetInstance(); } /** * Returns reference to the database of user records that Acedia was * set up to use. * * `Database` returned by this method stores persistent user data, for * the database of users that joined during the current game session * @see `GetDatabase()`. * * @return Main `UserDatabase` that Acedia currently uses to load and * store user information. Guaranteed to be a valid non-`none` reference. */ public final function Database GetPersistentDatabase() { if (persistentDatabase != none) { persistentDatabase.NewRef(); } return persistentDatabase; } /** * Returns configured database link to the JSON object in which users' data * is stored. * * @return Database link to the JSON object in which users' data is stored. * Guaranteed to not be `none`. */ public final function Text GetPersistentDataLink() { return _.text.FromString(userdataDBLink); } /** * Checks whether database setup to store users' persistent data was configured * and actually exists. * * This does not check for whether that database is properly configured. * If sub-object set to store users' data was not created inside it, then * Acedia will not be able to make use of users' persistent storage. * * @return `true` if database for users' persistent data storage exists and * `false` otherwise. */ public final function bool PersistentStorageExists() { return (persistentDatabase != none); } /** * Fetches `User` object that stores persistent data for a given `userID`. * * @param userID ID for which to fetch a persistent data storage. * @return `User` object for a given `UserID`. Guaranteed to be a valid * non-`none` reference if passed `userID` is not `none` and initialized * (which is guaranteed unless you manually created it). */ public final function User Fetch(UserID userID) { local User result; local UserDatabase userDB; userDB = class'UserDatabase'.static.GetInstance(); if (userDB == none) { return none; } result = userDB.FetchUser(userID); userDB.FreeSelf(); return result; } /** * Fetches appropriate `User` object for a player, given his id as a `Text`. * * @param idHash `Text` representation of someone's id. * @return Corresponding `User` object. Guaranteed to be a valid non-`none` * reference. */ public final function User FetchByIDHash(BaseText idHash) { local UserID userID; local UserDatabase userDB; local User result; userDB = class'UserDatabase'.static.GetInstance(); if (userDB == none) { return none; } userID = userDB.FetchUserID(idHash); userDB.FreeSelf(); if (userID == none) { return none; } result = userDB.FetchUser(userID); userID.FreeSelf(); return result; } /** * Fetches appropriate `User` object for a player, given his session key. * * @param userKey Key corresponding to a `User` method must to get. * @return Corresponding `User` object. Guaranteed to be a valid non-`none` * reference if `userKey` was actually assigned to any `User` during * current playing session; `none` otherwise. */ public final function User FetchByKey(int userKey) { local User result; local UserDatabase userDB; userDB = class'UserDatabase'.static.GetInstance(); if (userDB != none) { return none; } result = userDB.FetchUserByKey(userKey); userDB.FreeSelf(); return result; } /** * Returns names of all available groups that users can belong to. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is configured to load user * groups from a database (either local or remote), the returned value is * a locally cached one. This helps us avoid having to query database each time * we want to check something about user groups, but it also means we might * have an outdated information. * * @return Array with names of all available groups. * All array elements are guaranteed to be not-`none`, unique and in * lower case. */ public final function array GetAvailableGroups() { local array emptyResult; if (usersFeature != none) { return usersFeature.GetAvailableGroups(); } return emptyResult; } /** * Adds a new user group. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is configured to load user * groups from a database (either local or remote), the returned value is * a locally cached one. This helps us avoid having to query database each time * we want to check something about user groups, but it also means we might * have an outdated information. * Changes will always persist for the duration of the match, but writing * them into the (non-config) source might fail, leading to changes being reset * after the level switch. For non-database (config) sources changes will * always be saved. * * @param groupName Name of the group to add. Case-insensitive. * @return `true` if group was added and `false` otherwise (including if it * already existed). */ public final function bool AddGroup(BaseText groupName) { if (usersFeature != none) { return usersFeature.AddGroup(groupName); } return false; } /** * Removes existing user group. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is configured to load user * groups from a database (either local or remote), the returned value is * a locally cached one. This helps us avoid having to query database each time * we want to check something about user groups, but it also means we might * have an outdated information. * Changes will always persist for the duration of the match, but writing * them into the (non-config) source might fail, leading to changes being reset * after the level switch. For non-database (config) sources changes will * always be saved. * * @param groupName Name of the group to remove. Case-insensitive. * @return `true` if group was removed and `false` otherwise (including if it * didn't exist in the first place). */ public final function bool RemoveGroup(BaseText groupName) { if (usersFeature != none) { return usersFeature.RemoveGroup(groupName); } return false; } /** * Checks whether group with specified name exists. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is configured to load user * groups from a database (either local or remote), the returned value is * a locally cached one. This helps us avoid having to query database each time * we want to check something about user groups, but it also means we might * have an outdated information. * * @param groupName Name of the group to check existence of. * Case-insensitive. * @return `true` if group exists and `false` otherwise. */ public final function bool IsGroupExisting(BaseText groupName) { if (usersFeature != none) { return usersFeature.IsGroupExisting(groupName); } return false; } /** * Adds user with the given SteamID into the specified group. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is configured to load user * groups from a database (either local or remote), changes are guaranteed to * be made to the locally cached copy that will persist for the duration of * the game. Method will also attempt to change the database value, but that is * not guaranteed to succeed, meaning that changes might not be saved for * later matches. * * @param steamID SteamID of the user to add to the group. * @param groupName Name of the group to add user to. Case-insensitive. * @return `true` if user was added to the group (including if her was already * added to it) and `false` in any other case. */ public final function bool AddSteamIDToGroup( BaseText steamID, BaseText groupName) { if (usersFeature != none) { return usersFeature.AddSteamIDToGroup(steamID, groupName); } return false; } /** * Adds user with the given SteamID into the specified group. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is configured to load user * groups from a database (either local or remote), changes are guaranteed to * be made to the locally cached copy that will persist for the duration of * the game. Method will also attempt to change the database value, but that is * not guaranteed to succeed, meaning that changes might not be saved for * later matches. * * @param steamID SteamID of the user to add to the group. * @param groupName Name of the group to add user to. Case-insensitive. * @return `true` if user was added to the group (including if her was already * added to it) and `false` in any other case. */ public final /*unreal*/ function bool AddSteamIDToGroup_S( string steamID, string groupName) { if (usersFeature != none) { return usersFeature.AddSteamIDToGroup_S(steamID, groupName); } return false; } /** * Adds user (given by the `UserID`) into the specified group. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is configured to load user * groups from a database (either local or remote), changes are guaranteed to * be made to the locally cached copy that will persist for the duration of * the game. Method will also attempt to change the database value, but that is * not guaranteed to succeed, meaning that changes might not be saved for * later matches. * * @param id `UserID` of the user to add to the group. * @param groupName Name of the group to add user to. Case-insensitive. * @return `true` if user was added to the group (including if her was already * added to it) and `false` in any other case. */ public final function bool AddUserIDToGroup( UserID id, BaseText groupName) { if (usersFeature != none) { return usersFeature.AddUserIDToGroup(id, groupName); } return false; } /** * Adds given user into the specified group. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is configured to load user * groups from a database (either local or remote), changes are guaranteed to * be made to the locally cached copy that will persist for the duration of * the game. Method will also attempt to change the database value, but that is * not guaranteed to succeed, meaning that changes might not be saved for * later matches. * * @param user User to add to the group. * @param groupName Name of the group to add user to. Case-insensitive. * @return `true` if user was added to the group (including if her was already * added to it) and `false` in any other case. */ public final function bool AddUserToGroup(User user, BaseText groupName) { if (usersFeature != none) { return usersFeature.AddUserToGroup(user, groupName); } return false; } /** * Removes user with the given SteamID from the specified group. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is configured to load user * groups from a database (either local or remote), changes are guaranteed to * be made to the locally cached copy that will persist for the duration of * the game. Method will also attempt to change the database value, but that is * not guaranteed to succeed, meaning that changes might not be saved for * later matches. * * @param steamID SteamID of the user to remove to the group. * @param groupName Name of the group to remove user to. Case-insensitive. * @return `true` if user was removed to the group (including if her was * already removed to it) and `false` in any other case. */ public final function bool RemoveSteamIDFromGroup( BaseText steamID, BaseText groupName) { if (usersFeature != none) { return usersFeature.RemoveSteamIDFromGroup(steamID, groupName); } return false; } /** * Removes user with the given SteamID from the specified group. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is configured to load user * groups from a database (either local or remote), changes are guaranteed to * be made to the locally cached copy that will persist for the duration of * the game. Method will also attempt to change the database value, but that is * not guaranteed to succeed, meaning that changes might not be saved for * later matches. * * @param steamID SteamID of the user to remove to the group. * @param groupName Name of the group to remove user to. Case-insensitive. * @return `true` if user was removed to the group (including if her was * already removed to it) and `false` in any other case. */ public final /*unreal*/ function bool RemoveSteamIDFromGroup_S( string steamID, string groupName) { if (usersFeature != none) { return usersFeature.RemoveSteamIDFromGroup_S(steamID, groupName); } return false; } /** * Removes user (given by the `UserID`) from the specified group. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is configured to load user * groups from a database (either local or remote), changes are guaranteed to * be made to the locally cached copy that will persist for the duration of * the game. Method will also attempt to change the database value, but that is * not guaranteed to succeed, meaning that changes might not be saved for * later matches. * * @param id `UserID` of the user to remove to the group. * @param groupName Name of the group to remove user to. Case-insensitive. * @return `true` if user was removed to the group (including if her was * already removed to it) and `false` in any other case. */ public final function bool RemoveUserIDFromGroup( UserID id, BaseText groupName) { if (usersFeature != none) { return usersFeature.RemoveUserIDFromGroup(id, groupName); } return false; } /** * Removes user from the specified group. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is configured to load user * groups from a database (either local or remote), changes are guaranteed to * be made to the locally cached copy that will persist for the duration of * the game. Method will also attempt to change the database value, but that is * not guaranteed to succeed, meaning that changes might not be saved for * later matches. * * @param user User to remove to the group. * @param groupName Name of the group to remove user to. Case-insensitive. * @return `true` if user was removed to the group (including if her was * already removed to it) and `false` in any other case. */ public final function bool RemoveUserFromGroup(User user, BaseText groupName) { if (usersFeature != none) { return usersFeature.RemoveUserFromGroup(user, groupName); } return false; } /** * Returns names of all groups available for the user given by the SteamID. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is set up to load user groups * from a database (either local or remote), the returned value is a locally * cached one. This helps us avoid having to query database each time we want * to check something about user groups, but it also means we might have * an outdated information. * * @see `GetGroupsForUserID()` / `GetGroupsForUser()`. * * @param steamID SteamID of the user. * Must be specified in a SteamID64 format, e.g. "76561197960287930". * @return Array with names of the groups of the specified user. * All array elements are guaranteed to be not-`none`, unique and in * lower case. * If passed SteamID is `none` or data wasn't yet loaded - returns empty * array. */ public final function array GetGroupsForSteamID( BaseText steamID) { local array emptyResult; if (usersFeature != none) { return usersFeature.GetGroupsForSteamID(steamID); } return emptyResult; } /** * Returns names of all groups available for the user given by the SteamID. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is set up to load user groups * from a database (either local or remote), the returned value is a locally * cached one. This helps us avoid having to query database each time we want * to check something about user groups, but it also means we might have * an outdated information. * * @see `GetGroupsForUserID()` / `GetGroupsForUser()`. * * @param steamID SteamID of the user. * Must be specified in a SteamID64 format, e.g. "76561197960287930". * @return Array with names of the groups of the specified user. * All array elements are guaranteed to be not-`none`, unique and in * lower case. * If data wasn't yet loaded - returns empty array. */ public final /*unreal*/ function array GetGroupsForSteamID_S( string steamID) { local array emptyResult; if (usersFeature != none) { return usersFeature.GetGroupsForSteamID_S(steamID); } return emptyResult; } /** * Returns names of all groups available for the user given by the `UserID`. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is set up to load user groups * from a database (either local or remote), the returned value is a locally * cached one. This helps us avoid having to query database each time we want * to check something about user groups, but it also means we might have * an outdated information. * * @see `GetGroupsForSteamID()` / `GetGroupsForUser()`. * * @param id ID of the user. * @return Array with names of the groups of the specified user. * All array elements are guaranteed to be not-`none`, unique and in * lower case. * If data wasn't yet loaded - returns empty array. */ public final function array GetGroupsForUserID(UserID id) { local array emptyResult; if (usersFeature != none) { return usersFeature.GetGroupsForUserID(id); } return emptyResult; } /** * Returns names of all groups available for the user. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is set up to load user groups * from a database (either local or remote), the returned value is a locally * cached one. This helps us avoid having to query database each time we want * to check something about user groups, but it also means we might have * an outdated information. * * @see `GetGroupsForSteamID()` / `GetGroupsForUserID()`. * * @param user Reference to `User` object that represent the user we are to * find groups for. * @return Array with names of the groups of the specified user. * All array elements are guaranteed to be not-`none`, unique and in * lower case. * If data wasn't yet loaded - returns empty array. */ public final function array GetGroupsForUser(User user) { local array emptyResult; if (usersFeature != none) { return usersFeature.GetGroupsForUser(user); } return emptyResult; } /** * Returns `UserID`s of all users that belong into the group named `groupName`. * * @see For more information alongside `UserID`s use * `GetAnnotatedGroupMembers()`. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is set up to load user groups * from a database (either local or remote), the returned value is a locally * cached one. This helps us avoid having to query database each time we want * to check something about user groups, but it also means we might have * an outdated information. * * @param groupName Name of the group. Case-insensitive. * @return Array with `UserID`s for every user in the user group named * `groupName`. All array elements are guaranteed to be not-`none` and * correspond to unique players. * If data wasn't yet loaded - returns empty array. */ public final function array GetGroupMembers(BaseText groupName) { local array emptyResult; if (usersFeature != none) { return usersFeature.GetGroupMembers(groupName); } return emptyResult; } /** * Returns annotated `UserID`s of all users that belong into the group named * `groupName`. `UserID`s aren't necessarily human-readable (e.g. SteamID) * and to help organize configs they can be annotated with a `Text` name. * This method returns `UserID` alongside such annotation, if it exists. * * @see For just `UserID`s use `GetGroupMembers()`. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is set up to load user groups * from a database (either local or remote), the returned value is a locally * cached one. This helps us avoid having to query database each time we want * to check something about user groups, but it also means we might have * an outdated information. * * @param groupName Name of the group. Case-insensitive. * @return Array with `UserID`s for every user in the user group named * `groupName`. All array elements are guaranteed to be not-`none` and * correspond to unique players. * If data wasn't yet loaded - returns empty array. */ public final function array GetAnnotatedGroupMembers( BaseText groupName) { local array emptyResult; if (usersFeature != none) { return usersFeature.GetAnnotatedGroupMembers(groupName); } return emptyResult; } /** * Checks whether user given by the `UserID` belongs to the group named * `groupName`. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is set up to load user groups * from a database (either local or remote), the returned value is a locally * cached one. This helps us avoid having to query database each time we want * to check something about user groups, but it also means we might have * an outdated information. * * @param id ID of the user to check. * @param groupName Name of the group. Case-insensitive. * @return `true` if user with an ID given by `id` belongs to the group named * `groupName` and false if: it does not, either of the parameters is * invalid or group data wasn't yet properly loaded. */ public final function bool IsUserIDInGroup(UserID id, Text groupName) { if (usersFeature != none) { return usersFeature.IsUserIDInGroup(id, groupName); } return false; } /** * Checks whether user belongs to the given group. * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is set up to load user groups * from a database (either local or remote), the returned value is a locally * cached one. This helps us avoid having to query database each time we want * to check something about user groups, but it also means we might have * an outdated information. * * @param user Reference to `User` object that represent the user we are to * check for belonging to the group. * @param groupName Name of the group. Case-insensitive. * @return `true` if user with an ID given by `id` belongs to the group named * `groupName` and false if: it does not, either of the parameters is * invalid or group data wasn't yet properly loaded. */ public final function bool IsUserInGroup(User user, Text groupName) { if (usersFeature != none) { return usersFeature.IsUserInGroup(user, groupName); } return false; } /** * Checks whether user groups' data was already loaded from the source * (either config file or local/remote database). * * Will only work if `Users_Feature` is active. * Data loaded once is cached and this method returning `true` does not * guarantee that is isn't outdated. Additional, asynchronous queries must be * made to check for that. * * @return `true` if user groups' data was loaded and `false` otherwise. */ public final function bool IsUserGroupDataLoaded() { if (usersFeature != none) { return usersFeature.IsUserGroupDataLoaded(); } return false; } defaultproperties { userdataDBLink = "[local]database:/users" warnNoPersistentDatabaseLink = (l=LOG_Warning,m="No persistent user database link is setup. No persistent user data or user groups will be available. Setup `userDataDBLink` inside \"AcediaSystem.ini\".") errCannotCreateSkeletonFor = (l=LOG_Error,m="Failed to create persistent database skeleton for connected database with link \"%1\". User data functionality won't function properly.") errNoPersistentDatabase = (l=LOG_Error,m="Failed to connect to persistent user database with link \"%1\").") infoPersistentDatabaseLoaded = (l=LOG_Info,m="Connected to persistent user database with link \"%1\".") }