Skip to content

Commit 8013feb

Browse files
authored
Fix newbie messages not firing for new players on modern Paper (#6571)
Player#hasPlayedBefore() is unreliable since the switch to configuration-phase events, as player data is persisted during login, so genuine first-joiners were treated as returning players. This broke EssentialsSpawn newbie spawn/kit/announce-format (#6466). Add a firstJoin flag to AsyncUserDataLoadEvent, derived from EssentialsX's own user data, and use it in EssentialsSpawn and EssentialsDiscord instead of hasPlayedBefore(). Fixes #6466
1 parent 83aaf8c commit 8013feb

5 files changed

Lines changed: 36 additions & 11 deletions

File tree

‎Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,12 @@ private void joinFlow(final User user, final long currentTime, final String mess
408408

409409
final boolean newUsername = lastAccountName != null && !lastAccountName.equals(user.getBase().getName());
410410

411+
// A null last account name means EssentialsX has never recorded this player before, i.e. it's their first join.
412+
// We rely on EssentialsX's own user data here rather than Player#hasPlayedBefore(), which is unreliable on modern
413+
// server platforms that persist player data during the login/configuration phase.
414+
// See https://github.com/EssentialsX/Essentials/issues/6466, https://github.com/EssentialsX/Essentials/issues/6464
415+
final boolean firstJoin = lastAccountName == null;
416+
411417
// If the Minecraft account name changed, reset the nickname so the old one doesn't persist
412418
if (ess.getSettings().isResetNickOnNameChange() && newUsername && user.getNickname() != null) {
413419
user.setNickname(null);
@@ -463,7 +469,7 @@ private void joinFlow(final User user, final long currentTime, final String mess
463469
joinMessageConsumer.accept(effectiveMessage);
464470
}
465471

466-
ess.runTaskAsynchronously(() -> ess.getServer().getPluginManager().callEvent(new AsyncUserDataLoadEvent(user, effectiveMessage)));
472+
ess.runTaskAsynchronously(() -> ess.getServer().getPluginManager().callEvent(new AsyncUserDataLoadEvent(user, effectiveMessage, firstJoin)));
467473

468474
if (ess.getSettings().getMotdDelay() >= 0) {
469475
final int motdDelay = ess.getSettings().getMotdDelay() / 50;

‎Essentials/src/main/java/net/essentialsx/api/v2/events/AsyncUserDataLoadEvent.java‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ public class AsyncUserDataLoadEvent extends Event {
1313

1414
private final IUser user;
1515
private final String joinMessage;
16+
private final boolean firstJoin;
1617

17-
public AsyncUserDataLoadEvent(IUser user, String joinMessage) {
18+
public AsyncUserDataLoadEvent(IUser user, String joinMessage, boolean firstJoin) {
1819
super(true);
1920
this.user = user;
2021
this.joinMessage = joinMessage;
22+
this.firstJoin = firstJoin;
2123
}
2224

2325
/**
@@ -34,6 +36,18 @@ public String getJoinMessage() {
3436
return joinMessage;
3537
}
3638

39+
/**
40+
* Whether this is the first time EssentialsX has seen this player join.
41+
* <p>
42+
* This is determined from EssentialsX's own user data rather than {@link org.bukkit.OfflinePlayer#hasPlayedBefore()},
43+
* which is unreliable on modern server platforms that persist player data during the login/configuration phase.
44+
*
45+
* @return {@code true} if this is the player's first recorded join, otherwise {@code false}.
46+
*/
47+
public boolean isFirstJoin() {
48+
return firstJoin;
49+
}
50+
3751
@Override
3852
public HandlerList getHandlers() {
3953
return handlers;

‎EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitListener.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public void onJoin(AsyncUserDataLoadEvent event) {
101101
// Delay join to let nickname load
102102
if (!isSilentJoinQuit(event.getUser(), "join") && !isVanishHide(event.getUser())) {
103103
// Check if this is the first time the player has joined
104-
if (!event.getUser().getBase().hasPlayedBefore()) {
104+
if (event.isFirstJoin()) {
105105
sendJoinQuitMessage(event.getUser().getBase(), event.getJoinMessage(), MessageType.DefaultTypes.FIRST_JOIN);
106106
} else {
107107
sendJoinQuitMessage(event.getUser().getBase(), event.getJoinMessage(), MessageType.DefaultTypes.JOIN);

‎EssentialsSpawn/src/main/java/com/earth2me/essentials/spawn/EssentialsSpawn.java‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
import com.earth2me.essentials.EssentialsLogger;
44
import com.earth2me.essentials.metrics.MetricsWrapper;
55
import net.ess3.api.IEssentials;
6+
import net.essentialsx.api.v2.events.AsyncUserDataLoadEvent;
67
import org.bukkit.Location;
78
import org.bukkit.command.Command;
89
import org.bukkit.command.CommandSender;
910
import org.bukkit.event.EventPriority;
10-
import org.bukkit.event.player.PlayerJoinEvent;
1111
import org.bukkit.event.player.PlayerRespawnEvent;
1212
import org.bukkit.plugin.PluginManager;
1313
import org.bukkit.plugin.java.JavaPlugin;
@@ -48,8 +48,11 @@ public void onEnable() {
4848

4949
final EventPriority joinPriority = ess.getSettings().getSpawnJoinPriority();
5050
if (joinPriority != null) {
51-
pluginManager.registerEvent(PlayerJoinEvent.class, playerListener, joinPriority, (ll, event) ->
52-
((EssentialsSpawnPlayerListener) ll).onPlayerJoin((PlayerJoinEvent) event), this);
51+
// Listen on AsyncUserDataLoadEvent (fired after the core join flow) so we can reliably tell whether this is
52+
// a player's first join, instead of using Player#hasPlayedBefore() which doesn't since data is persisted during the configuration phase.
53+
// See https://github.com/EssentialsX/Essentials/issues/6466
54+
pluginManager.registerEvent(AsyncUserDataLoadEvent.class, playerListener, joinPriority, (ll, event) ->
55+
((EssentialsSpawnPlayerListener) ll).onUserDataLoad((AsyncUserDataLoadEvent) event), this);
5356
}
5457

5558
if (metrics == null) {

‎EssentialsSpawn/src/main/java/com/earth2me/essentials/spawn/EssentialsSpawnPlayerListener.java‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
import com.earth2me.essentials.textreader.KeywordReplacer;
88
import com.earth2me.essentials.utils.VersionUtil;
99
import net.ess3.api.IEssentials;
10+
import net.essentialsx.api.v2.events.AsyncUserDataLoadEvent;
1011
import org.bukkit.Location;
1112
import org.bukkit.entity.Player;
1213
import org.bukkit.event.Listener;
13-
import org.bukkit.event.player.PlayerJoinEvent;
1414
import org.bukkit.event.player.PlayerRespawnEvent;
1515
import org.bukkit.event.player.PlayerTeleportEvent;
1616
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
@@ -76,12 +76,14 @@ void onPlayerRespawn(final PlayerRespawnEvent event) {
7676
}
7777
}
7878

79-
void onPlayerJoin(final PlayerJoinEvent event) {
80-
ess.runTaskAsynchronously(() -> delayedJoin(event.getPlayer()));
79+
void onUserDataLoad(final AsyncUserDataLoadEvent event) {
80+
// AsyncUserDataLoadEvent is already fired asynchronously, after EssentialsX has determined whether this is a
81+
// first join, so we can jump straight into the join handling without racing the core join flow.
82+
delayedJoin(event.getUser().getBase(), event.isFirstJoin());
8183
}
8284

83-
private void delayedJoin(final Player player) {
84-
if (player.hasPlayedBefore()) {
85+
private void delayedJoin(final Player player, final boolean firstJoin) {
86+
if (!firstJoin) {
8587
logger.log(Level.FINE, "Old player join");
8688
final List<String> spawnOnJoinGroups = ess.getSettings().getSpawnOnJoinGroups();
8789
if (!spawnOnJoinGroups.isEmpty()) {

0 commit comments

Comments
 (0)