package de.photon.aacadditionpro.user; import de.photon.aacadditionpro.InternalPermission; import de.photon.aacadditionpro.modules.ModuleType; import de.photon.aacadditionpro.user.subdata.FishingData; import de.photon.aacadditionpro.user.subdata.InventoryData; import de.photon.aacadditionpro.user.subdata.KeepAliveData; import de.photon.aacadditionpro.user.subdata.LookPacketData; import de.photon.aacadditionpro.user.subdata.ScaffoldData; import de.photon.aacadditionpro.user.subdata.TowerData; import de.photon.aacadditionpro.util.mathematics.Hitbox; import lombok.Getter; import org.bukkit.GameMode; import org.bukkit.entity.Player; @Getter public class User { private Player player; private TimestampMap<TimestampKey> timestampMap; private ObjectDataMap<DataKey> dataMap; private FishingData fishingData = new FishingData(this); private InventoryData inventoryData = new InventoryData(this); private KeepAliveData keepAliveData = new KeepAliveData(this); private LookPacketData lookPacketData = new LookPacketData(this); private ScaffoldData scaffoldData = new ScaffoldData(this); private TowerData towerData = new TowerData(this); public User(final Player player) { this.player = player; UserManager.setVerbose(this, InternalPermission.AAC_VERBOSE.hasPermission(player)); // Timestamps this.timestampMap = new TimestampMap<>(TimestampKey.class); this.timestampMap.nullifyTimeStamps(TimestampKey.values()); // Login time this.getTimestampMap().updateTimeStamp(TimestampKey.LOGIN_TIME); // Login should count as movement. this.getTimestampMap().updateTimeStamp(TimestampKey.LAST_HEAD_OR_OTHER_MOVEMENT); this.getTimestampMap().updateTimeStamp(TimestampKey.LAST_XYZ_MOVEMENT); this.getTimestampMap().updateTimeStamp(TimestampKey.LAST_XZ_MOVEMENT); // Data this.dataMap = new ObjectDataMap<>(DataKey.class, (key, value) -> value == null || key.getClazz().isAssignableFrom(value.getClass())); for (DataKey value : DataKey.values()) { this.dataMap.setValue(value, value.getDefaultValue()); } } // Basics /** * This checks if this {@link User} still exists and should be checked. * * @param user the {@link User} to be checked. * @param moduleType the {@link ModuleType} that should be used to determine if the {@link User} is bypassed. * * @return true if the {@link User} is null or bypassed. */ public static boolean isUserInvalid(final User user, final ModuleType moduleType) { return user == null || user.getPlayer() == null || user.isBypassed(moduleType); } /** * Determines whether a {@link User} bypasses a certain {@link ModuleType}. */ public boolean isBypassed(ModuleType moduleType) { return InternalPermission.hasPermission(this.player, InternalPermission.BYPASS.getRealPermission() + '.' + moduleType.getConfigString().toLowerCase()); } /** * Checks if the {@link User} is in {@link GameMode#ADVENTURE} or {@link GameMode#SURVIVAL} */ public boolean inAdventureOrSurvivalMode() { return this.player.getGameMode() == GameMode.ADVENTURE || this.player.getGameMode() == GameMode.SURVIVAL; } /** * This determines and returnes the correct {@link Hitbox} for this {@link User}. * * @return {@link Hitbox#SNEAKING_PLAYER} or {@link Hitbox#PLAYER}. */ public Hitbox getHitbox() { return this.player.isSneaking() ? Hitbox.SNEAKING_PLAYER : Hitbox.PLAYER; } // Inventory /** * Determines whether the {@link User} has a currently opened {@link org.bukkit.inventory.Inventory} according to * {@link de.photon.aacadditionpro.AACAdditionPro}s internal data. */ public boolean hasOpenInventory() { return this.getTimestampMap().getTimeStamp(TimestampKey.INVENTORY_OPENED) != 0; } /** * Determines if this {@link User} has not had an open inventory for some amount of time. * * @param milliseconds the amount of time in milliseconds that the {@link User} should not have interacted with an * {@link org.bukkit.inventory.Inventory}. */ public boolean notRecentlyOpenedInventory(final long milliseconds) { return !this.getTimestampMap().recentlyUpdated(TimestampKey.INVENTORY_OPENED, milliseconds); } /** * Determines if this {@link User} has recently clicked in an {@link org.bukkit.inventory.Inventory}. * * @param milliseconds the amount of time in milliseconds in which the {@link User} should be checked for * interactions with an {@link org.bukkit.inventory.Inventory}. */ public boolean hasClickedInventoryRecently(final long milliseconds) { return this.getTimestampMap().recentlyUpdated(TimestampKey.LAST_INVENTORY_CLICK, milliseconds); } // Movement /** * Checks if this {@link User} has moved recently. * * @param movementType what movement should be checked * @param milliseconds the amount of time in milliseconds that should be considered. * * @return true if the player has moved in the specified time frame. */ public boolean hasMovedRecently(final TimestampKey movementType, final long milliseconds) { switch (movementType) { case LAST_HEAD_OR_OTHER_MOVEMENT: case LAST_XYZ_MOVEMENT: case LAST_XZ_MOVEMENT: return this.timestampMap.recentlyUpdated(movementType, milliseconds); default: throw new IllegalStateException("Unexpected MovementType: " + movementType); } } /** * Checks if this {@link User} has sprinted recently * * @param milliseconds the amount of time in milliseconds that should be considered. * * @return true if the player has sprinted in the specified time frame. */ public boolean hasSprintedRecently(final long milliseconds) { return this.dataMap.getBoolean(DataKey.SPRINTING) || this.timestampMap.recentlyUpdated(TimestampKey.LAST_SPRINT_TOGGLE, milliseconds); } /** * Checks if this {@link User} has sneaked recently * * @param milliseconds the amount of time in milliseconds that should be considered. * * @return true if the player has sneaked in the specified time frame. */ public boolean hasSneakedRecently(final long milliseconds) { return this.dataMap.getBoolean(DataKey.SNEAKING) || this.timestampMap.recentlyUpdated(TimestampKey.LAST_SNEAK_TOGGLE, milliseconds); } // Convenience methods for much used timestamps /** * Determines if this {@link User} has recently teleported. * This includes ender pearls as well as respawns, world changes and ordinary teleports. * * @param milliseconds the amount of time in milliseconds that should be considered. */ public boolean hasTeleportedRecently(final long milliseconds) { return this.getTimestampMap().recentlyUpdated(TimestampKey.LAST_TELEPORT, milliseconds); } /** * Determines if this {@link User} has recently respawned. * * @param milliseconds the amount of time in milliseconds that should be considered. */ public boolean hasRespawnedRecently(final long milliseconds) { return this.getTimestampMap().recentlyUpdated(TimestampKey.LAST_RESPAWN, milliseconds); } /** * Determines if this {@link User} has recently changed worlds. * * @param milliseconds the amount of time in milliseconds that should be considered. */ public boolean hasChangedWorldsRecently(final long milliseconds) { return this.getTimestampMap().recentlyUpdated(TimestampKey.LAST_WORLD_CHANGE, milliseconds); } /** * Determines if this {@link User} has recently logged in. * * @param milliseconds the amount of time in milliseconds that should be considered. */ public boolean hasLoggedInRecently(final long milliseconds) { return this.getTimestampMap().recentlyUpdated(TimestampKey.LOGIN_TIME, milliseconds); } // Skin /** * Updates the saved skin components. * * @return true if the skinComponents changed and there have already been some skin components beforehand. */ public boolean updateSkinComponents(int newSkinComponents) { Integer oldSkin = (Integer) this.getDataMap().getValue(DataKey.SKIN_COMPONENTS); if (oldSkin == null) { this.getDataMap().setValue(DataKey.SKIN_COMPONENTS, newSkinComponents); return false; } if (oldSkin == newSkinComponents) { return false; } this.getDataMap().setValue(DataKey.SKIN_COMPONENTS, newSkinComponents); return true; } // Disabling, equals() and hashCode() /** * This method unregisters the {@link User} to make sure that memory leaks will not happen, and if they do, * their impact is very small. */ public void unregister() { this.player = null; this.timestampMap.clear(); this.timestampMap = null; this.dataMap.clear(); this.dataMap = null; this.fishingData = null; this.inventoryData = null; this.keepAliveData = null; this.lookPacketData = null; this.scaffoldData = null; this.towerData = null; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; return this.player.getUniqueId().equals(((User) o).player.getUniqueId()); } @Override public int hashCode() { return 47 + (this.player == null ? 0 : player.getUniqueId().hashCode()); } }