Skip to content

Commit

Permalink
Fixes #83
Browse files Browse the repository at this point in the history
Minor JavaDoc cleanup
Implemented sessions in a way that isn't destructive to the current way of tracking AFK state
  • Loading branch information
Dart2112 committed Jan 31, 2025
1 parent ee4036f commit 7cf9caf
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 22 deletions.
1 change: 1 addition & 0 deletions .idea/dictionaries/benja.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 12 additions & 2 deletions src/main/java/net/lapismc/afkplus/AFKPlus.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import net.lapismc.afkplus.commands.AFK;
import net.lapismc.afkplus.commands.AFKPlusCmd;
import net.lapismc.afkplus.playerdata.AFKPlusPlayer;
import net.lapismc.afkplus.playerdata.AFKSession;
import net.lapismc.afkplus.util.AFKPlusConfiguration;
import net.lapismc.afkplus.util.AFKPlusContext;
import net.lapismc.lapiscore.LapisCorePlugin;
Expand All @@ -44,16 +45,17 @@ public final class AFKPlus extends LapisCorePlugin {
public PrettyTime prettyTime;
public LapisUpdater updater;
private final HashMap<UUID, AFKPlusPlayer> players = new HashMap<>();
private final HashMap<UUID, AFKSession> playerSessions = new HashMap<>();
private AFKPlusListeners listeners;

@Override
public void onEnable() {
saveDefaultConfig();
registerConfiguration(new AFKPlusConfiguration(this, 17, 6));
registerConfiguration(new AFKPlusConfiguration(this, 18, 7));
registerPermissions(new AFKPlusPermissions(this));
registerLuckPermsContext();
update();
new LapisCoreFileWatcher(this);
fileWatcher = new LapisCoreFileWatcher(this);
Locale loc = new Locale(config.getMessage("PrettyTimeLocale"));
prettyTime = new PrettyTime(loc);
prettyTime.removeUnit(JustNow.class);
Expand Down Expand Up @@ -93,6 +95,14 @@ public AFKPlusPlayer getPlayer(OfflinePlayer op) {
return getPlayer(op.getUniqueId());
}

public AFKSession getPlayerSession(UUID uuid) {
return playerSessions.getOrDefault(uuid, null);
}

public void storeAFKSession(AFKSession session) {
playerSessions.put(session.getUUID(), session);
}

private void update() {
//Don't check for updates if the update check setting is set to false
if (!getConfig().getBoolean("UpdateCheck"))
Expand Down
17 changes: 10 additions & 7 deletions src/main/java/net/lapismc/afkplus/AFKPlusListeners.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import net.lapismc.afkplus.api.AFKMachineDetectEvent;
import net.lapismc.afkplus.playerdata.AFKPlusPlayer;
import net.lapismc.afkplus.playerdata.AFKSession;
import net.lapismc.afkplus.util.EntitySpawnManager;
import net.lapismc.afkplus.util.PlayerMovementMonitoring;
import net.lapismc.afkplus.util.PlayerMovementStorage;
Expand Down Expand Up @@ -63,17 +64,19 @@ public class AFKPlusListeners implements Listener {

@EventHandler
public void onPlayerJoin(PlayerJoinEvent e) {
//TODO: Check here if the player has been offline for some amount of time.
//If they have only recently left then we wont run forceStop since it triggers an interact
//This will stop players from reconnecting to reset their AFK timer
//If the player has been offline for more than a few minutes, we can run forceStop as we used to
//This will ensure that all settings are reset to start tracking AFK time and interacts from right now
plugin.getPlayer(e.getPlayer()).forceStopAFK();
//Load the players session if one is stored
AFKSession session = plugin.getPlayerSession(e.getPlayer().getUniqueId());
//If the session is null, it means we didn't have one stored, so run the old code
//But otherwise we let the session handle it
if (session == null)
plugin.getPlayer(e.getPlayer()).forceStopAFK();
else
session.processReconnect(plugin.getPlayer(e.getPlayer()));
}

@EventHandler
public void onPlayerQuit(PlayerQuitEvent e) {
plugin.getPlayer(e.getPlayer()).stopAFK(true);
plugin.storeAFKSession(new AFKSession(plugin, plugin.getPlayer(e.getPlayer())));
}

@EventHandler
Expand Down
76 changes: 65 additions & 11 deletions src/main/java/net/lapismc/afkplus/playerdata/AFKPlusPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public AFKPlusPlayer(AFKPlus plugin, UUID uuid) {
/**
* Get the UUID of the player
*
* @return Returns the UUID of the player
* @return the UUID of the player
*/
public UUID getUUID() {
return uuid;
Expand All @@ -73,7 +73,7 @@ public UUID getUUID() {
/**
* Get the players username
*
* @return Returns the name of the player
* @return the name of the player
*/
public String getName() {
return Bukkit.getOfflinePlayer(uuid).getName();
Expand Down Expand Up @@ -104,7 +104,7 @@ public boolean isInactive() {
* 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
* @return true if the player DOESN'T have the permission
*/
public boolean isNotPermitted(Permission perm) {
return !plugin.perms.isPermitted(uuid, perm.getPermission());
Expand All @@ -126,10 +126,28 @@ public void warnPlayer() {
playSound("WarningSound", XSound.ENTITY_PLAYER_LEVELUP);
}

/**
* Check if the user has received a warning about being acted upon for being AFK
*
* @return true if the player has been warned, otherwise false
*/
public boolean isWarned() {
return isWarned;
}

/**
* Set the warned state of the user, this is only used when resuming sessions
*
* @param isWarned the desired state of isWarned
*/
protected void setIsWarned(boolean isWarned) {
this.isWarned = isWarned;
}

/**
* Check if the player is AFK
*
* @return returns true if the player is currently AFK
* @return true if the player is currently AFK
*/
public boolean isAFK() {
return isAFK;
Expand All @@ -138,22 +156,40 @@ public boolean isAFK() {
/**
* Check if the players AFK state is fake
*
* @return returns true if the player is both AFK and the AFK state is faked
* @return true if the player is both AFK and the AFK state is faked
*/
public boolean isFakeAFK() {
return isAFK && isFakeAFK;
}

/**
* Set the fake AFK attribute after the fact, this is only used to restore sessions
*
* @param isFakeAFK the desired value for isFakeAFK
*/
protected void setFakeAFK(boolean isFakeAFK) {
this.isFakeAFK = 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
* @return the System.currentTimeMillis() when the player was set AFK
*/
public Long getAFKStart() {
return afkStart;
}

/**
* Set the time when the player entered AFK, this is used for resuming AFK after reconnect
*
* @param afkStart The UNIX Epoch of when the player entered AFK
*/
protected void setAFKStart(Long afkStart) {
this.afkStart = afkStart;
}

/**
* Starts AFK for this player with a broadcast, Use {@link #forceStartAFK()} for silent AFK
* This can be cancelled with {@link AFKStartEvent}
Expand Down Expand Up @@ -286,7 +322,7 @@ public void forceStopAFK() {
/**
* Check if the player's AFK status should be broadcast to other players
*
* @return Returns whether the player's AFK message should be broadcast to other players
* @return whether the player's AFK message should be broadcast to other players
*/
public boolean shouldBroadcastToOthers() {
boolean vanish = plugin.getConfig().getBoolean("Broadcast.Vanish");
Expand Down Expand Up @@ -353,8 +389,8 @@ public void takeAction() {
* This will stop AFK if a player is AFK and update the lastInteract value
*/
public void interact() {
//Don't allow interact when the player is inactive
//Inactive is decided by the listener class checking location data
//Don't allow the player to interact when the player is inactive
//Inactivity is decided by the listener class checking location data
if (isInactive)
return;
lastInteract = System.currentTimeMillis();
Expand All @@ -363,10 +399,28 @@ public void interact() {
stopAFK();
}

/**
* This is the UNIX Epoch at the time that the player last triggered an interact event based on the current config
*
* @return the last time the player interacted
*/
public Long getLastInteract() {
return lastInteract;
}

/**
* Set the last interact time for this player, used when resuming sessions
*
* @param lastInteract the UNIX Epoch at the time the player last interacted with the world
*/
protected void setLastInteract(Long lastInteract) {
this.lastInteract = lastInteract;
}

/**
* Check if a player is currently vanished
*
* @return Returns true if the player is currently vanished
* @return true if the player is currently vanished
*/
public boolean isVanished() {
if (!isOnline()) {
Expand Down Expand Up @@ -492,7 +546,7 @@ private void recordTimeStatistic() {
* It is run every second by default
* This should not be used else where
*
* @return Returns the runnable used for AFK detection
* @return the runnable used for AFK detection
*/
public Runnable getRepeatingTask() {
return () -> {
Expand Down
101 changes: 101 additions & 0 deletions src/main/java/net/lapismc/afkplus/playerdata/AFKSession.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2025 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 org.bukkit.Bukkit;

import java.util.UUID;

/**
* A class for tracking players who have disconnected from the server
* This class is used to ensure that players cannot skirt AFK tracking by relogging
*/
public class AFKSession {

private final AFKPlus plugin;
private final UUID playerUUID;
private final boolean isAFK, isFakeAFK, isWarned, isInactive;
private final Long disconnectTime, relativeLastInteract, relativeAFKStart;

/**
* Create a session for the provided player and record the disconnect time as now
*
* @param plugin The plugins main class for config access
* @param player The player who is disconnecting
*/
public AFKSession(AFKPlus plugin, AFKPlusPlayer player) {
this.plugin = plugin;
playerUUID = player.getUUID();
isAFK = player.isAFK();
relativeAFKStart = System.currentTimeMillis() - player.getAFKStart();
isFakeAFK = player.isFakeAFK();
isWarned = player.isWarned();
isInactive = player.isInactive();
relativeLastInteract = System.currentTimeMillis() - player.getLastInteract();
disconnectTime = System.currentTimeMillis();
}

/**
* Process a player reconnecting, this is mostly setting AFKPlayer values
*
* @param p The player who has connected
*/
public void processReconnect(AFKPlusPlayer p) {
//Check if it's a short enough offline time to process this as a reconnect vs a new session
//Milliseconds since the user disconnected
long millisOffline = System.currentTimeMillis() - disconnectTime;
//Minutes since the user disconnected
float minutesOffline = millisOffline / 1000.0f / 60.0f;
double sessionLength = plugin.getConfig().getDouble("SessionLength");
if (sessionLength < minutesOffline) {
//The user has been offline longer than the session length
//Therefore we ignore the users session and reset them
p.forceStopAFK();
//return so that the session logic is skipped
return;
}
if (isAFK) {
//If the player was AFK, set them as AFK and set the AFKTime
p.forceStartAFK();
//Calculate what the AFK start time would've been if the disconnect didn't happen
Long AFKStart = System.currentTimeMillis() - relativeAFKStart;
p.setAFKStart(AFKStart);
//Make sure we update the FakeAFK status
if (isFakeAFK)
p.setFakeAFK(true);
if (isWarned)
p.setIsWarned(true);
//Send a message to the player to let them know that their AFK state has been resumed
Bukkit.getScheduler().runTaskLater(plugin, () ->
Bukkit.getPlayer(playerUUID).sendMessage(plugin.config.getMessage("Self.Resume")), 20);
}
p.setInactive(isInactive);
//Set the last interact time based on the value at disconnect
p.setLastInteract(System.currentTimeMillis() - relativeLastInteract);
}

/**
* Get the UUID of the player that this class represents
*
* @return a player UUID
*/
public UUID getUUID() {
return playerUUID;
}

}
7 changes: 6 additions & 1 deletion src/main/resources/config.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ConfigVersion: 17
ConfigVersion: 18

#Should the plugin check for new updates, UpdateDownload will not work if this is set to false
UpdateCheck: true
Expand All @@ -23,6 +23,11 @@ Commands:
#Setting to 0 means that players will always be acted upon when they reach their time to action
ActionPlayerRequirement: 0

#Session Length refers to how long a player needs to be offline before their AFK state is reset
#If a player quits while AFK and rejoins before this number of minutes, they will be set as AFK automatically on join
#This value is in minutes, you can enter fractions of a minute, e.g. 0.5 = 30 seconds
SessionLength: 5.0

#Enabling this setting will make AFKPlus update a players AFK status in essentials to match their AFKPlus AFK state
#This may be useful for other plugins that check if a player is AFK by checking with Essentials
EssentialsAFKHook: false
Expand Down
4 changes: 3 additions & 1 deletion src/main/resources/messages.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ConfigVersion: 6
ConfigVersion: 7

#Primary and secondary colors will replace &p and &s in any messages
PrimaryColor: "&6"
Expand All @@ -23,6 +23,8 @@ Self:
Start: "{PREFIX} &sYou&p are now AFK"
#You may also add a {TIME} variable that will be replaced by how long the player was AFK
Stop: "{PREFIX} &sYou&p are no longer AFK"
#The message sent to players when they join and are immediately set as AFK
Resume: "{PREFIX} &pYour AFK State has been resumed!"

Updater:
NoUpdate: "&pThere is no update available"
Expand Down

0 comments on commit 7cf9caf

Please sign in to comment.