diff --git a/src/main/java/com/gmail/nossr50/datatypes/player/PersistentPlayerData.java b/src/main/java/com/gmail/nossr50/datatypes/player/PersistentPlayerData.java new file mode 100644 index 000000000..8cf157545 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/datatypes/player/PersistentPlayerData.java @@ -0,0 +1,455 @@ +package com.gmail.nossr50.datatypes.player; + +import com.gmail.nossr50.api.exceptions.UnexpectedValueException; +import com.gmail.nossr50.config.AdvancedConfig; +import com.gmail.nossr50.config.Config; +import com.gmail.nossr50.datatypes.MobHealthBarType; +import com.gmail.nossr50.datatypes.dirtydata.DirtyData; +import com.gmail.nossr50.datatypes.dirtydata.DirtyDataMap; +import com.gmail.nossr50.datatypes.mutableprimitives.MutableBoolean; +import com.gmail.nossr50.datatypes.mutableprimitives.MutableInteger; +import com.gmail.nossr50.datatypes.mutableprimitives.MutableLong; +import com.gmail.nossr50.datatypes.mutableprimitives.MutableString; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.datatypes.skills.SuperAbilityType; +import com.gmail.nossr50.datatypes.validation.NonNullRule; +import com.gmail.nossr50.datatypes.validation.PositiveIntegerRule; +import com.gmail.nossr50.datatypes.validation.Validator; +import com.gmail.nossr50.util.experience.MMOExperienceBarManager; +import org.apache.commons.lang.NullArgumentException; +import org.jetbrains.annotations.NotNull; + +import java.util.EnumMap; +import java.util.Map; +import java.util.UUID; + +public class PersistentPlayerData { + + /* Player Stuff */ + private final @NotNull DirtyData playerName; + private final @NotNull UUID playerUUID; + + /* Records */ + private final DirtyData lastLogin; + + /* HUDs */ + private final @NotNull DirtyData mobHealthBarType; + private final @NotNull MutableBoolean dirtyFlag; //Dirty values in this class will change this flag as needed + + /* Skill Data */ + private final @NotNull DirtyDataMap skillLevelValues; + private final @NotNull DirtyDataMap skillExperienceValues; + private final @NotNull DirtyDataMap abilityDeactivationTimestamps; // Ability & Cooldown + private final @NotNull DirtyDataMap uniquePlayerData; //Misc data that doesn't fit into other categories (chimaera wing, etc..) + private final @NotNull DirtyDataMap barStateMap; + + /* Special Flags */ + private final @NotNull DirtyData partyChatSpying; + private final @NotNull DirtyData leaderBoardExclusion; + + /* Scoreboards */ + private final @NotNull DirtyData scoreboardTipsShown; + + /** + * Create new persistent player data for a player + * Initialized with default values + * @param playerUUID target player's UUID + * @param playerName target player's name + * @throws NullArgumentException thrown when never null arguments are null + */ + public PersistentPlayerData(@NotNull UUID playerUUID, @NotNull String playerName) throws NullArgumentException { + /* + * New Data + */ + this.dirtyFlag = new MutableBoolean(false); //Set this one first + this.playerUUID = playerUUID; + this.playerName = new DirtyData<>(new MutableString(playerName), dirtyFlag); + + this.skillLevelValues = new DirtyDataMap<>(new EnumMap<>(PrimarySkillType.class), dirtyFlag); + this.skillExperienceValues = new DirtyDataMap<>(new EnumMap<>(PrimarySkillType.class), dirtyFlag); + this.abilityDeactivationTimestamps = new DirtyDataMap<>(new EnumMap<>(SuperAbilityType.class), dirtyFlag); + this.uniquePlayerData = new DirtyDataMap<>(new EnumMap<>(UniqueDataType.class), dirtyFlag); + this.mobHealthBarType = new DirtyData<>(Config.getInstance().getMobHealthbarDefault(), dirtyFlag); + + this.scoreboardTipsShown = new DirtyData<>(new MutableInteger(0), dirtyFlag); + + for(SuperAbilityType superAbilityType : SuperAbilityType.values()) { + abilityDeactivationTimestamps.put(superAbilityType, 0); + } + + for(PrimarySkillType primarySkillType : PrimarySkillType.values()) { + skillLevelValues.put(primarySkillType, AdvancedConfig.getInstance().getStartingLevel()); + skillExperienceValues.put(primarySkillType, 0F); + } + + //Unique Player Data + this.uniquePlayerData.put(UniqueDataType.CHIMAERA_WING_DATS, 0); + + this.partyChatSpying = new DirtyData<>(new MutableBoolean(false), dirtyFlag); + + this.barStateMap = new DirtyDataMap<>(MMOExperienceBarManager.generateDefaultBarStateMap(), dirtyFlag); + this.lastLogin = new DirtyData<>(new MutableLong(0), dirtyFlag); //Value of 0 will represent that the user hasn't been seen online + this.leaderBoardExclusion = new DirtyData<>(new MutableBoolean(false), dirtyFlag); + } + + /** + * Create persistent player data for a player using existing values + * Typically this will be used when loading data + * @param playerUUID target player's {@link UUID} + * @param playerName target player's saved name + * @param partyChatSpying target player's chat spy toggle + * @param skillLevelValues target player's skill levels + * @param skillExperienceValues target player's skill experience levels + * @param abilityDeactivationTimestamps target player's ability deactivation time stamps + * @param uniquePlayerData target player's misc unique data + * @param barStateMap target player's XP bar state settings + * @param scoreboardTipsShown target player's scoreboard tip view count + * @param mobHealthBarType target player's mob health bar type + * @param lastLogin target player's last login + * @param leaderBoardExclusion target player's leaderboard exemption status + */ + public PersistentPlayerData(@NotNull UUID playerUUID, + @NotNull String playerName, + boolean partyChatSpying, + @NotNull EnumMap skillLevelValues, + @NotNull EnumMap skillExperienceValues, + @NotNull EnumMap abilityDeactivationTimestamps, + @NotNull EnumMap uniquePlayerData, + @NotNull EnumMap barStateMap, + int scoreboardTipsShown, + @NotNull MobHealthBarType mobHealthBarType, + long lastLogin, + boolean leaderBoardExclusion) throws Exception { + + /* + * Skills Data + */ + this.dirtyFlag = new MutableBoolean(false); //Set this one first + + validateMap(skillLevelValues); + this.skillLevelValues = new DirtyDataMap<>(skillLevelValues, dirtyFlag); + + validateMap(skillExperienceValues); + this.skillExperienceValues = new DirtyDataMap<>(skillExperienceValues, dirtyFlag); + + validateMap(abilityDeactivationTimestamps); + this.abilityDeactivationTimestamps = new DirtyDataMap<>(abilityDeactivationTimestamps, dirtyFlag); + + this.uniquePlayerData = new DirtyDataMap<>(uniquePlayerData, dirtyFlag); + + this.scoreboardTipsShown = new DirtyData<>(new MutableInteger(scoreboardTipsShown), dirtyFlag); + this.mobHealthBarType = new DirtyData<>(mobHealthBarType, dirtyFlag); + + this.playerUUID = playerUUID; + this.playerName = new DirtyData<>(new MutableString(playerName), dirtyFlag); + this.barStateMap = new DirtyDataMap<>(barStateMap, dirtyFlag); + + this.partyChatSpying = new DirtyData<>(new MutableBoolean(partyChatSpying), dirtyFlag); + this.lastLogin = new DirtyData<>(new MutableLong(lastLogin), dirtyFlag); + + this.leaderBoardExclusion = new DirtyData<>(new MutableBoolean(leaderBoardExclusion), dirtyFlag); + } + + /** + * Makes sure a target map only contains positive numbers and no null values for its keyset + * @param hashMap target map + * @throws UnexpectedValueException when values are outside of expected norms + * @throws Exception when values are outside of expected norms + */ + private void validateMap(Map, ? extends Number> hashMap) throws UnexpectedValueException, Exception { + Validator validator = new Validator<>(); + + validator.addRule(new PositiveIntegerRule<>()); + validator.addRule(new NonNullRule<>()); + + for(PrimarySkillType primarySkillType : PrimarySkillType.values()) { + validator.validate(hashMap.get(primarySkillType)); + } + } + + /** + * Set the level of a Primary Skill for the Player + * @param primarySkillType target Primary Skill + * @param newSkillLevel the new value of the skill + */ + public void setSkillLevel(PrimarySkillType primarySkillType, int newSkillLevel) { + skillLevelValues.put(primarySkillType, newSkillLevel); + } + + /** + * Get the skill level the player currently has for target Primary Skill + * @param primarySkillType target Primary Skill + * @return the current level value of target Primary Skill + */ + public Integer getSkillLevel(PrimarySkillType primarySkillType) { + return skillLevelValues.get(primarySkillType); + } + + /** + * True if the persistent data has changed state and not yet saved to DB + * @return true if data is dirty (not saved) + */ + public boolean isDirtyProfile() { + return dirtyFlag.getImmutableCopy(); + } + + /** + * Set the dirty flag back to false + * Should be called after saving the player data to avoid unnecessary saves + */ + public void resetDirtyFlag() { + dirtyFlag.setBoolean(false); + } + + /** + * The saved player name for the player associated with this data + * @return the saved player name for the player associated with this data + */ + public @NotNull String getPlayerName() { + return playerName.getData().getImmutableCopy(); + } + + /** + * The {@link UUID} for the player associated with this data + * @return the UUID for the player associated with this data + */ + public @NotNull UUID getPlayerUUID() { + return playerUUID; + } + + /** + * This player's saved mob health bar type + * @return the saved mob health bar type for this player + */ + public @NotNull MobHealthBarType getMobHealthBarType() { + return mobHealthBarType.getData(); + } + + /** + * Change the mob health bar type for this player + * @param mobHealthBarType the new mob health bar type for this player + */ + public void setMobHealthBarType(@NotNull MobHealthBarType mobHealthBarType) { + this.mobHealthBarType.setData(mobHealthBarType); + } + + /* + * Party Chat Spy + */ + + /** + * Whether or not this player is currently spying on all party chat + * @return true if this player is spying on party chat + */ + public boolean isPartyChatSpying() { return partyChatSpying.getData().getImmutableCopy(); } + + /** + * Toggle this player's party chat spying + */ + public void togglePartyChatSpying() { + partyChatSpying.getData().setBoolean(!partyChatSpying.getData().getImmutableCopy()); + } + + /** + * Modify whether or not this player is spying on party chat + * @param bool the new value of party chat spying (true for spying, false for not spying) + */ + public void setPartyChatSpying(boolean bool) { + this.partyChatSpying.getData().setBoolean(bool); + } + + /* + * Scoreboards + */ + + /** + * The currently tracked number of times scoreboard tips have been viewed for this player + * @return the currently tracked number of times scoreboard tips have been viewed for this player + */ + public int getScoreboardTipsShown() { + return scoreboardTipsShown.getData(false).getImmutableCopy(); + } + + /** + * Modify the count of how many times scoreboard tips have been displayed to this player + * @param newValue the new value + */ + public void setScoreboardTipsShown(int newValue) { + scoreboardTipsShown.getData(true).setInt(newValue); + } + + /* + * Cooldowns + */ + + /** + * The time stamp for the last Chimaera Wing use for this player + * @return the Chimaera Wing last use time stamp for this player + */ + public int getChimaeraWingDATS() { + return uniquePlayerData.get((UniqueDataType.CHIMAERA_WING_DATS)); + } + + /** + * Set the time stamp for Chimaera Wing's last use for this player + * @param DATS the new time stamp + */ + private void setChimaeraWingDATS(int DATS) { + uniquePlayerData.put(UniqueDataType.CHIMAERA_WING_DATS, DATS); + } + + /** + * Change one of the unique data map entries + * @param uniqueDataType target unique data + * @param newData new unique data value + */ + public void setUniqueData(@NotNull UniqueDataType uniqueDataType, int newData) { + uniquePlayerData.put(uniqueDataType, newData); + } + + /** + * Get the value associated with a specific {@link UniqueDataType} + * @param uniqueDataType target unique data + * @return associated value of this unique data + */ + public long getUniqueData(@NotNull UniqueDataType uniqueDataType) { return uniquePlayerData.get(uniqueDataType); } + + /** + * Get the current deactivation timestamp of an superAbilityType. + * + * @param superAbilityType The {@link SuperAbilityType} to get the DATS for + * @return the deactivation timestamp for the superAbilityType + */ + public long getAbilityDATS(@NotNull SuperAbilityType superAbilityType) { + return abilityDeactivationTimestamps.get(superAbilityType); + } + + /** + * Set the current deactivation timestamp of an superAbilityType. + * + * @param superAbilityType The {@link SuperAbilityType} to set the DATS for + * @param DATS the DATS of the superAbilityType + */ + public void setAbilityDATS(@NotNull SuperAbilityType superAbilityType, long DATS) { + abilityDeactivationTimestamps.put(superAbilityType, (int) (DATS * .001D)); + } + + /** + * Reset all ability cooldowns. + */ + public void resetCooldowns() { + abilityDeactivationTimestamps.replaceAll((a, v) -> 0); + } + + /** + * Get the {@link Map} for the related {@link com.gmail.nossr50.util.experience.MMOExperienceBarManager.BarState}'s of this player + * @return the bar state map for this player + */ + public @NotNull Map getBarStateMap() { + return barStateMap.getDataMap(); + } + + /** + * Get the {@link DirtyDataMap} for the related {@link com.gmail.nossr50.util.experience.MMOExperienceBarManager.BarState}'s of this player + * @return the dirty bar state map for this player + */ + public @NotNull DirtyDataMap getDirtyBarStateMap() { + return barStateMap; + } + + /** + * Get the {@link DirtyDataMap} for the skill levels of this player + * @return the dirty skill level map for this player + */ + public @NotNull DirtyDataMap getDirtySkillLevelMap() { + return skillLevelValues; + } + + /** + * Get the {@link DirtyDataMap} for the skill experience values of this player + * @return the dirty skill experience values map for this player + */ + public @NotNull DirtyDataMap getDirtyExperienceValueMap() { + return skillExperienceValues; + } + + /** + * Get the {@link DirtyData} for the party chat toggle for this player + * @return the dirty data for the party chat toggle for this player + */ + public @NotNull DirtyData getDirtyPartyChatSpying() { + return partyChatSpying; + } + + /** + * Get the skill level map for this player + * @return the map of skill levels for this player + */ + public @NotNull Map getSkillLevelsMap() { + return skillLevelValues.getDataMap(); + } + + /** + * Get the map of experience values for skills for this player + * @return the experience values map for this player + */ + public @NotNull Map getSkillsExperienceMap() { + return skillExperienceValues.getDataMap(); + } + + /** + * Get the map of timestamps representing the last use of abilities for this player + * @return the ability deactivation timestamps map for this player + */ + public @NotNull Map getAbilityDeactivationTimestamps() { + return abilityDeactivationTimestamps.getDataMap(); + } + + /** + * Get a map of various unique data for this player + * @return a map of unique data for this player + */ + public @NotNull Map getUniquePlayerData() { + return uniquePlayerData.getDataMap(); + } + + /** + * Mark this data as dirty which will flag this data for the next appropriate save + * Saves happen periodically, they also can happen on server shutdown and when the player disconnects from the server + */ + public void setDirtyProfile() { + this.dirtyFlag.setBoolean(true); + } + + /** + * The timestamp of when this player last logged in + * @return the timestamp of when this player last logged in + */ + public long getLastLogin() { + return lastLogin.getData().getImmutableCopy(); + } + + /** + * Set the value of when this player last logged in + * @param newValue the new time stamp + */ + public void setLastLogin(long newValue) { + lastLogin.getData().setLong(newValue); + } + + /** + * Whether or not this player is exempt from leader boards + * @return true if excluded from leader boards + */ + public boolean isLeaderBoardExcluded() { + return leaderBoardExclusion.getData().getImmutableCopy(); + } + + /** + * Set whether or not this player is excluded from leader boards + * @param bool new value + */ + public void setLeaderBoardExclusion(boolean bool) { + leaderBoardExclusion.getData(true).setBoolean(bool); + } +} diff --git a/src/main/java/com/gmail/nossr50/datatypes/player/PersistentPlayerDataBuilder.java b/src/main/java/com/gmail/nossr50/datatypes/player/PersistentPlayerDataBuilder.java new file mode 100644 index 000000000..37b7a5ee9 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/datatypes/player/PersistentPlayerDataBuilder.java @@ -0,0 +1,257 @@ +package com.gmail.nossr50.datatypes.player; + +import com.gmail.nossr50.datatypes.MobHealthBarType; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.datatypes.skills.SuperAbilityType; +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.util.experience.MMOExperienceBarManager; +import org.apache.commons.lang.NullArgumentException; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.EnumMap; +import java.util.UUID; + +public class PersistentPlayerDataBuilder { + + /* Player Stuff */ + private @Nullable String playerName; + private @Nullable UUID playerUUID; + + /* Records */ + private long lastLogin; + + /* HUDs */ + private @Nullable MobHealthBarType mobHealthBarType; + + /* Skill Data */ + private @Nullable EnumMap skillLevelValues; + private @Nullable EnumMap skillExperienceValues; + private @Nullable EnumMap abilityDeactivationTimestamps; // Ability & Cooldown + private @Nullable EnumMap uniquePlayerData; //Misc data that doesn't fit into other categories (chimaera wing, etc..) + private @Nullable EnumMap barStateMap; + + /* Special Flags */ + private boolean partyChatSpying; + private boolean leaderBoardExemption = false; + + /* Scoreboards */ + private int scoreboardTipsShown; + + public @NotNull PersistentPlayerData buildNewPlayerData(@NotNull Player player) { + /* + * New Profile with default values + */ + return buildNewPlayerData(player.getUniqueId(), player.getName()); + } + + public @Nullable PersistentPlayerData buildNewPlayerData(@NotNull OfflinePlayer offlinePlayer) { + if(offlinePlayer.getName() != null) + return buildNewPlayerData(offlinePlayer.getUniqueId(), offlinePlayer.getName()); + else + return null; + } + + public @NotNull PersistentPlayerData buildNewPlayerData(@NotNull UUID playerUUID, @NotNull String playerName) { + /* + * New Profile with default values + */ + return new PersistentPlayerData(playerUUID, playerName); + } + + public @NotNull PersistentPlayerData build() throws Exception { + if(playerUUID == null) + throw new NullArgumentException("playerUUID"); + + if(playerName == null) + throw new NullArgumentException("player name"); + + if(skillLevelValues == null) + throw new NullArgumentException("skillLevelValues"); + + validateSkillLevelMapEntries(skillLevelValues); + + if(skillExperienceValues == null) + throw new NullArgumentException("skillExperienceValues"); + + validateExperienceValueMapEntries(skillExperienceValues); + + if(abilityDeactivationTimestamps == null) + throw new NullArgumentException("abilityDeactivationTimestamps"); + + validateAbilityCooldownMapEntries(abilityDeactivationTimestamps); + + if(uniquePlayerData == null) + throw new NullArgumentException("uniquePlayerData"); + + validateUniquePlayerDataMapEntries(uniquePlayerData); + + if(barStateMap == null) + throw new NullArgumentException("barStateMap"); + + validateBarStateMapEntries(barStateMap); + + if(mobHealthBarType == null) + throw new NullArgumentException("mobHealthBarType"); + + + return new PersistentPlayerData(playerUUID, playerName, partyChatSpying, skillLevelValues, skillExperienceValues, abilityDeactivationTimestamps, uniquePlayerData, barStateMap, scoreboardTipsShown, mobHealthBarType, lastLogin, leaderBoardExemption); + } + + private void validateBarStateMapEntries(@NotNull EnumMap map) { + EnumMap barMapDefaults = MMOExperienceBarManager.generateDefaultBarStateMap(); + + for(PrimarySkillType primarySkillType : PrimarySkillType.values()) { + map.putIfAbsent(primarySkillType, barMapDefaults.get(primarySkillType)); + } + } + + private void validateExperienceValueMapEntries(@NotNull EnumMap map) { + for(PrimarySkillType key : PrimarySkillType.values()) { + map.putIfAbsent(key, 0F); + + if(map.get(key) < 0F) { + mcMMO.p.getLogger().severe("found negative value for map entry of " + key.toString() + " for player " + playerName + " , resetting to 0"); + map.put(key, 0F); + } + } + } + + private void validateUniquePlayerDataMapEntries(@NotNull EnumMap map) { + for(UniqueDataType key : UniqueDataType.values()) { + map.putIfAbsent(key, 0); + + if(map.get(key) < 0) { + mcMMO.p.getLogger().severe("found negative value for map entry of " + key.toString() + " for player " + playerName + " , resetting to 0"); + map.put(key, 0); + } + } + } + + private void validateAbilityCooldownMapEntries(@NotNull EnumMap map) { + for(SuperAbilityType key : SuperAbilityType.values()) { + map.putIfAbsent(key, 0); + + if(map.get(key) < 0) { + mcMMO.p.getLogger().severe("found negative value for map entry of " + key.toString() + " for player " + playerName + " , resetting to 0"); + map.put(key, 0); + } + } + } + + private void validateSkillLevelMapEntries(@NotNull EnumMap map) { + for(PrimarySkillType key : PrimarySkillType.values()) { + map.putIfAbsent(key, 0); + + if(map.get(key) < 0) { + mcMMO.p.getLogger().severe("found negative value for map entry of " + key.toString() + " for player " + playerName + " , resetting to 0"); + map.put(key, 0); + } + } + } + + public @Nullable String getPlayerName() { + return playerName; + } + + public @NotNull PersistentPlayerDataBuilder setPlayerName(@NotNull String playerName) { + this.playerName = playerName; + return this; + } + + public @Nullable UUID getPlayerUUID() { + return playerUUID; + } + + public @NotNull PersistentPlayerDataBuilder setPlayerUUID(@NotNull UUID playerUUID) { + this.playerUUID = playerUUID; + return this; + } + + public long getLastLogin() { + return lastLogin; + } + + public @NotNull PersistentPlayerDataBuilder setLastLogin(long lastLogin) { + this.lastLogin = lastLogin; + return this; + } + + public @Nullable MobHealthBarType getMobHealthBarType() { + return mobHealthBarType; + } + + public @NotNull PersistentPlayerDataBuilder setMobHealthBarType(@NotNull MobHealthBarType mobHealthBarType) { + this.mobHealthBarType = mobHealthBarType; + return this; + } + + public @Nullable EnumMap getSkillLevelValues() { + return skillLevelValues; + } + + public @NotNull PersistentPlayerDataBuilder setSkillLevelValues(@NotNull EnumMap skillLevelValues) { + this.skillLevelValues = skillLevelValues; + return this; + } + + public @Nullable EnumMap getSkillExperienceValues() { + return skillExperienceValues; + } + + public @NotNull PersistentPlayerDataBuilder setSkillExperienceValues(@NotNull EnumMap skillExperienceValues) { + this.skillExperienceValues = skillExperienceValues; + return this; + } + + public @Nullable EnumMap getAbilityDeactivationTimestamps() { + return abilityDeactivationTimestamps; + } + + public @NotNull PersistentPlayerDataBuilder setAbilityDeactivationTimestamps(@NotNull EnumMap abilityDeactivationTimestamps) { + this.abilityDeactivationTimestamps = abilityDeactivationTimestamps; + return this; + } + + public @Nullable EnumMap getUniquePlayerData() { + return uniquePlayerData; + } + + public @NotNull PersistentPlayerDataBuilder setUniquePlayerData(@NotNull EnumMap uniquePlayerData) { + this.uniquePlayerData = uniquePlayerData; + return this; + } + + public @Nullable EnumMap getBarStateMap() { + return barStateMap; + } + + public @NotNull PersistentPlayerDataBuilder setBarStateMap(@NotNull EnumMap barStateMap) { + this.barStateMap = barStateMap; + return this; + } + + public @NotNull PersistentPlayerDataBuilder setPartyChatSpying(boolean partyChatSpying) { + this.partyChatSpying = partyChatSpying; + return this; + } + + public int getScoreboardTipsShown() { + return scoreboardTipsShown; + } + + public @NotNull PersistentPlayerDataBuilder setScoreboardTipsShown(int scoreboardTipsShown) { + this.scoreboardTipsShown = scoreboardTipsShown; + return this; + } + + public boolean isPartyChatSpying() { + return partyChatSpying; + } + + public void setLeaderBoardExemption(boolean bool) { + leaderBoardExemption = bool; + } +}