All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.lapismc.afkplus.playerdata.AFKPlusPlayer Maven / Gradle / Ivy

There is a newer version: 3.4.1
Show newest version
/*
 * Copyright 2022 Benjamin Martin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.lapismc.afkplus.playerdata;

import net.lapismc.afkplus.AFKPlus;
import net.lapismc.afkplus.api.AFKActionEvent;
import net.lapismc.afkplus.api.AFKStartEvent;
import net.lapismc.afkplus.api.AFKStopEvent;
import net.lapismc.afkplus.util.EssentialsAFKHook;
import net.lapismc.lapiscore.compatibility.XSound;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Optional;
import java.util.UUID;

/**
 * This class can be used to start, stop and check AFK as well as the values used to start and stop AFK
 * Please read the documentation for each method before using it
 */
@SuppressWarnings("WeakerAccess")
public class AFKPlusPlayer {

    private final AFKPlus plugin;
    private final UUID uuid;
    private Long lastInteract;
    private Long afkStart;
    private boolean isAFK;
    private boolean isFakeAFK;
    private boolean isInactive;
    private boolean isWarned;

    /**
     * @param plugin The plugin instance for config and permissions access
     * @param uuid   The UUID of the player that this class should control
     */
    public AFKPlusPlayer(AFKPlus plugin, UUID uuid) {
        this.plugin = plugin;
        this.uuid = uuid;
        isAFK = false;
        isFakeAFK = false;
        isInactive = false;
        isWarned = false;
        lastInteract = System.currentTimeMillis();
    }

    /**
     * Get the UUID of the player
     *
     * @return Returns the UUID of the player
     */
    public UUID getUUID() {
        return uuid;
    }

    /**
     * Get the players username
     *
     * @return Returns the name of the player
     */
    public String getName() {
        return Bukkit.getOfflinePlayer(uuid).getName();
    }

    /**
     * Setting the player as inactive will stop {@link #interact()} from doing anything
     * This is used to deal with AFK machines
     *
     * @param isInactive true to enable blocking of {@link #interact()}
     */
    public void setInactive(boolean isInactive) {
        this.isInactive = isInactive;
    }

    /**
     * Check if the player is permitted to do something
     * Permissions are stored in {@link Permission} as an Enumeration
     *
     * @param perm The permission you wish to check
     * @return Returns true if the player DOESN'T have the permission
     */
    public boolean isNotPermitted(Permission perm) {
        return !plugin.perms.isPermitted(uuid, perm.getPermission());
    }

    /**
     * Warn the player with a message and sound if enabled
     */
    public void warnPlayer() {
        isWarned = true;
        Player p = Bukkit.getPlayer(uuid);
        if (p == null) {
            return;
        }
        //Send the player the warning message
        p.sendMessage(getMessage("Warning"));
        //Play the warning sound from the config
        playSound("WarningSound", XSound.ENTITY_PLAYER_LEVELUP);
    }

    /**
     * Check if the player is AFK
     *
     * @return returns true if the player is currently AFK
     */
    public boolean isAFK() {
        return isAFK;
    }

    /**
     * Check if the players AFK state is fake
     *
     * @return returns true if the player is both AFK and the AFK state is faked
     */
    public boolean isFakeAFK() {
        return isAFK && isFakeAFK;
    }

    /**
     * Get the system time when the player became AFK
     * Could be null if the player is not AFK
     *
     * @return Returns the System.currentTimeMillis() when the player was set AFK
     */
    public Long getAFKStart() {
        return afkStart;
    }

    /**
     * Starts AFK for this player with a broadcast, Use {@link #forceStartAFK()} for silent AFK
     * This can be cancelled with {@link AFKStartEvent}
     */
    public void startAFK() {
        if (Bukkit.getPlayer(getUUID()) == null) {
            //Player isn't online, stop here
            return;
        }
        //Get the command and message for the AFK start event
        String command = plugin.getConfig().getString("Commands.AFKStart");
        String message = getMessage("Broadcast.Start");
        //Call the AKFStart event
        AFKStartEvent event = new AFKStartEvent(this, command, message);
        Bukkit.getPluginManager().callEvent(event);
        if (event.isCancelled()) {
            return;
        }
        //Broadcast the AFK start message
        broadcast(event.getBroadcastMessage().replace("{PLAYER}", getName()));
        //Run AFK command
        runCommand(event.getCommand());
        //Play the AFK start sound
        playSound("AFKStartSound", XSound.BLOCK_ANVIL_HIT);
        //Start the AFK
        forceStartAFK();
    }

    /**
     * Overloads {@link #startAFK()} to set the AFK as fake
     *
     * @param isFake true if the player is to be set as "fake" AFK
     */
    public void startAFK(boolean isFake) {
        startAFK();
        //Check to make sure that AFK did start e.g. wasn't cancelled by the event API
        if (isAFK)
            isFakeAFK = isFake;
    }

    /**
     * Silently starts AFK for this player
     */
    public void forceStartAFK() {
        //Record the time that the player was set AFK
        afkStart = System.currentTimeMillis();
        //Set the player as AFK
        isAFK = true;
        //Update the players AFK status with the essentials plugin
        updateEssentialsAFKState();
        //Set if the player should be ignored for sleeping
        if (plugin.getConfig().getBoolean("IgnoreAFKPlayersForSleep")) {
            Player p = Bukkit.getPlayer(getUUID());
            if (p != null) {
                p.setSleepingIgnored(true);
            }
        }
    }

    /**
     * Stops AFK for this player with a broadcast, Use {@link #forceStopAFK()} for a silent stop
     * This can be cancelled with {@link AFKStopEvent}
     */
    public void stopAFK() {
        if (Bukkit.getPlayer(getUUID()) == null) {
            //Player isn't online, stop here
            return;
        }
        //Get the command and broadcast message
        String command = plugin.getConfig().getString("Commands.AFKStop");
        String message = getMessage("Broadcast.Stop");
        //Call the AKFStop event
        AFKStopEvent event = new AFKStopEvent(this, command, message);
        Bukkit.getPluginManager().callEvent(event);
        if (event.isCancelled()) {
            return;
        }
        runCommand(event.getCommand());
        //Get a string that is the user-friendly version of how long the player was AFK
        //This will replace the {TIME} variable, if present
        String afkTime = plugin.prettyTime.formatDuration(plugin.reduceDurationList
                (plugin.prettyTime.calculatePreciseDuration(new Date(afkStart))));
        broadcast(event.getBroadcastMessage().replace("{PLAYER}", getName()).replace("{TIME}", afkTime));
        //Stop the AFK status
        forceStopAFK();
    }

    /**
     * Silently stops AFK for this player
     */
    public void forceStopAFK() {
        //Record the new value for the time AFK statistic
        if (isAFK())
            recordTimeStatistic();
        //Reset warning
        isWarned = false;
        //Set player as no longer AFK
        isAFK = false;
        isFakeAFK = false;
        //Disable inactivity to allow the interact events to register
        isInactive = false;
        //Interact to update the last interact value
        interact();
        //Update the players AFK status with the essentials plugin
        updateEssentialsAFKState();
        //Set the player back to being counted for sleep counts
        if (plugin.getConfig().getBoolean("IgnoreAFKPlayersForSleep")) {
            Player p = Bukkit.getPlayer(getUUID());
            if (p != null) {
                p.setSleepingIgnored(false);
            }
        }
    }

    /**
     * Broadcast a message using the settings from the config
     *
     * @param msg The message you wish to broadcast
     */
    public void broadcast(String msg) {
        //Don't broadcast if the message is empty
        if (msg.isEmpty()) {
            return;
        }
        boolean vanish = plugin.getConfig().getBoolean("Broadcast.Vanish");
        boolean console = plugin.getConfig().getBoolean("Broadcast.Console");
        boolean otherPlayers = plugin.getConfig().getBoolean("Broadcast.OtherPlayers");
        boolean self = plugin.getConfig().getBoolean("Broadcast.Self");
        if (!vanish && isVanished()) {
            //Stop the broadcast from going to other players, but it still shows to the player and console
            otherPlayers = false;
        }
        Player player = Bukkit.getPlayer(getUUID());
        if (console) {
            Bukkit.getLogger().info(msg);
        }
        if (otherPlayers) {
            for (Player p : Bukkit.getOnlinePlayers()) {
                if (p.equals(player)) {
                    continue;
                }
                p.sendMessage(msg);
            }
        }
        if (self) {
            player.sendMessage(msg);
        }
    }

    /**
     * Runs the action command on this player
     */
    public void takeAction() {
        String command = plugin.getConfig().getString("Commands.Action");
        AFKActionEvent event = new AFKActionEvent(this, command);
        Bukkit.getPluginManager().callEvent(event);
        if (!event.isCancelled()) {
            forceStopAFK();
            runCommand(event.getCommand());
        }
    }

    /**
     * Log an interact, used by events for tracking when the player last did something
     * This will stop AFK if a player is AFK and update the lastInteract value
     */
    public void interact() {
        //Dont allow interact when the player is inactive
        //Inactive is decided by the listener class checking location data
        if (isInactive)
            return;
        lastInteract = System.currentTimeMillis();
        //Only take AFK stopping action if the player is AFK and not fake AFK
        if (isAFK && !isFakeAFK)
            stopAFK();
    }

    /**
     * Check if a player is currently vanished
     *
     * @return Returns true if the player is currently vanished
     */
    public boolean isVanished() {
        if (!Bukkit.getOfflinePlayer(getUUID()).isOnline()) {
            return false;
        }
        Player p = Bukkit.getPlayer(getUUID());
        for (MetadataValue meta : p.getMetadata("vanished")) {
            if (meta.asBoolean()) return true;
        }
        return false;
    }

    /**
     * Get the total time that a player has been AFK
     * This is the sum of all time that the player has been AFK
     *
     * @return The total time spent AFK, 0 if there is no record for this player
     */
    public long getTotalTimeAFK() {
        //Get or create the statistics file
        File f = new File(plugin.getDataFolder(), "statistics.yml");
        if (!f.exists()) {
            return 0L;
        }
        YamlConfiguration statistics = YamlConfiguration.loadConfiguration(f);
        //Grab the current value of the statistic so that we can add to it, or get 0L if there is no current value
        return statistics.getLong(getName() + ".TimeSpentAFK", 0L);
    }

    /**
     * Updates the players current AFK State within the essentials plugin
     */
    private void updateEssentialsAFKState() {
        //Update the AFK state with essentials if it is installed
        if (!Bukkit.getPluginManager().isPluginEnabled("Essentials"))
            return;
        EssentialsAFKHook essHook = new EssentialsAFKHook();
        essHook.setAFK(getUUID(), isAFK);
    }

    /**
     * Handles the running of a command with a player variable, this is used for AFK start/stop/warn/action commands
     *
     * @param command The command to be run with "[PLAYER]" in place of the players name
     */
    private void runCommand(String command) {
        //Ignore the command if it is blank, this is so that start/stop/warn events dont need to have commands
        if (command.equals(""))
            return;
        //Replace the player variable with the players name
        String cmd = command.replace("[PLAYER]", getName());
        //Dispatch the command on the next game tick
        Bukkit.getScheduler().runTask(plugin, () -> {
            boolean activeState = isInactive;
            isInactive = true;
            Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd);
            isInactive = activeState;
        });
    }

    /**
     * Uses the PAPI LapisCore integration to attempt placeholder replacement
     *
     * @param key The key for the message in the messages.yml
     * @return the formatted message
     */
    private String getMessage(String key) {
        return plugin.config.getMessage(key, Bukkit.getOfflinePlayer(getUUID()));
    }

    /**
     * Plays a sound from the config or the default sound if its not available
     *
     * @param pathToSound The path to the sounds name in the config.yml
     * @param def         The sound to be used if the sound in the config isn't valid
     */
    private void playSound(String pathToSound, XSound def) {
        Player p = Bukkit.getPlayer(getUUID());
        if (p == null || !p.isOnline())
            return;
        String soundName = plugin.getConfig().getString(pathToSound);
        if ("".equals(soundName) || soundName == null)
            return;
        XSound sound;
        Optional retrievedSound = XSound.matchXSound(soundName);
        sound = retrievedSound.orElse(def);
        sound.playSound(p);
    }

    /**
     * Records the time spent AFK and adds it to the already existing value in the statistics file
     */
    private void recordTimeStatistic() {
        //Get or create the statistics file
        File f = new File(plugin.getDataFolder(), "statistics.yml");
        if (!f.exists()) {
            try {
                if (!f.createNewFile())
                    throw new IOException("Failed to create " + f.getName());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        YamlConfiguration statistics = YamlConfiguration.loadConfiguration(f);
        //Grab the current value of the statistic so that we can add to it, or get 0L if there is no current value
        Long currentTimeSpendAFK = statistics.getLong(getName() + ".TimeSpentAFK", 0L);
        //Calculate the amount of time that the player was AFK for
        Long timeAFK = System.currentTimeMillis() - afkStart;
        //Set the value to be the old value plus the most recent amount of time AFK
        statistics.set(getName() + ".TimeSpentAFK", currentTimeSpendAFK + timeAFK);
        //Save the file
        try {
            statistics.save(f);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * This is the runnable that detects players who need to be set as AFK, warned or acted upon
     * It is run every second by default
     * This should not be used else where
     *
     * @return Returns the runnable used for AFK detection
     */
    public Runnable getRepeatingTask() {
        return () -> {
            if (Bukkit.getOfflinePlayer(getUUID()).isOnline()) {
                if (isAFK) {
                    boolean isAtPlayerRequirement;
                    int playersRequired = plugin.getConfig().getInt("ActionPlayerRequirement");
                    if (playersRequired == 0) {
                        isAtPlayerRequirement = true;
                    } else {
                        isAtPlayerRequirement = Bukkit.getOnlinePlayers().size() > playersRequired;
                    }
                    //Get the values that need to be met for warnings and action
                    Integer timeToWarning = plugin.perms.getPermissionValue(uuid, Permission.TimeToWarning.getPermission());
                    Integer timeToAction = plugin.perms.getPermissionValue(uuid, Permission.TimeToAction.getPermission());
                    //Get the number of seconds since the player went AFK
                    long secondsSinceAFKStart = (System.currentTimeMillis() - afkStart) / 1000;
                    //Don't check if we need to warn the player if waring is disabled
                    if (!timeToWarning.equals(-1)) {
                        //Check for warning
                        if (!isWarned && secondsSinceAFKStart >= timeToWarning) {
                            Bukkit.getScheduler().runTask(plugin, this::warnPlayer);
                        }
                    }
                    //Check if the player can have an action taken
                    if (!timeToAction.equals(-1)) {
                        //Check for action and if we are taking action yet
                        if (secondsSinceAFKStart >= timeToAction && isAtPlayerRequirement) {
                            Bukkit.getScheduler().runTask(plugin, this::takeAction);
                        }
                    }
                } else {
                    Integer timeToAFK = plugin.perms.getPermissionValue(uuid, Permission.TimeToAFK.getPermission());
                    //Check if the permission is 0 or -1
                    //-1 is for players who shouldn't be put into AFK by timer and 0 is to account for permission errors
                    if (timeToAFK.equals(-1) || timeToAFK.equals(0)) {
                        //This allows player to only be put into AFK by commands
                        return;
                    }
                    //Get the number of seconds since the last recorded interact
                    long secondsSinceLastInteract = (System.currentTimeMillis() - lastInteract) / 1000;
                    //Set them as AFK if it is the same or longer than the time to AFK
                    if (secondsSinceLastInteract >= timeToAFK) {
                        Bukkit.getScheduler().runTask(plugin, (Runnable) this::startAFK);
                    }
                }
            }
        };
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy