From 2df781a865fd6be69bdf415eceed18bfb215b2ce Mon Sep 17 00:00:00 2001 From: JRoy <10731363+JRoy@users.noreply.github.com> Date: Sat, 9 Aug 2025 20:53:56 -0700 Subject: [PATCH] Fix matching players with similar names --- .../com/earth2me/essentials/Essentials.java | 18 +++- .../earth2me/essentials/MatchUserTest.java | 82 +++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 Essentials/src/test/java/com/earth2me/essentials/MatchUserTest.java diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 756b1558e82..b7cb14d33c0 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -956,10 +956,16 @@ public User matchUser(final Server server, final User sourceUser, final String s try { exPlayer = server.getPlayer(UUID.fromString(searchTerm)); } catch (final IllegalArgumentException ex) { + // Prefer exact online name match first always if (getOffline) { + // When offline lookups are allowed, do not pick partial online matches here; allow exact offline match later exPlayer = server.getPlayerExact(searchTerm); } else { - exPlayer = server.getPlayer(searchTerm); + exPlayer = server.getPlayerExact(searchTerm); + if (exPlayer == null) { + // Only consider partial/prefix online match when not explicitly doing an offline-capable lookup + exPlayer = server.getPlayer(searchTerm); + } } } @@ -996,6 +1002,16 @@ public User matchUser(final Server server, final User sourceUser, final String s } } } else { + // Prefer exact username match among the matched players + for (final Player player : matches) { + if (player.getName().equalsIgnoreCase(searchTerm)) { + final User userMatch = getUser(player); + if (getHidden || canInteractWith(sourceUser, userMatch)) { + return userMatch; + } + } + } + // Then prefer display name/prefix match as before for (final Player player : matches) { final User userMatch = getUser(player); if (userMatch.getDisplayName().startsWith(searchTerm) && (getHidden || canInteractWith(sourceUser, userMatch))) { diff --git a/Essentials/src/test/java/com/earth2me/essentials/MatchUserTest.java b/Essentials/src/test/java/com/earth2me/essentials/MatchUserTest.java new file mode 100644 index 00000000000..2c62847591b --- /dev/null +++ b/Essentials/src/test/java/com/earth2me/essentials/MatchUserTest.java @@ -0,0 +1,82 @@ +package com.earth2me.essentials; + +import com.earth2me.essentials.commands.PlayerNotFoundException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockbukkit.mockbukkit.ServerMock; +import org.mockbukkit.mockbukkit.entity.PlayerMock; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class MatchUserTest { + private Essentials ess; + private ServerMock server; + + @BeforeEach + public void setUp() { + this.server = MockBukkit.mock(); + Essentials.TESTING = true; + ess = MockBukkit.load(Essentials.class); + } + + @AfterEach + public void tearDown() { + MockBukkit.unmock(); + } + + @Test + public void exactMatchPreferredOverPartialOnline() throws Exception { + final PlayerMock exactPlayer = server.addPlayer("Ginshin"); + final PlayerMock partialPlayer = server.addPlayer("Ginshin_BOT"); + final PlayerMock callerBase = server.addPlayer("Caller"); + + final User caller = ess.getUser(callerBase); + ess.getUser(exactPlayer); + ess.getUser(partialPlayer); + + final User matched = ess.matchUser(server, caller, "Ginshin", false, false); + assertEquals("Ginshin", matched.getName()); + } + + @Test + public void hiddenPlayerExactOnlyWhenOfflineLookupPlayerCaller() throws Exception { + final PlayerMock hiddenBase = server.addPlayer("Hidden"); + final PlayerMock callerBase = server.addPlayer("Caller"); + + final User hidden = ess.getUser(hiddenBase); + final User caller = ess.getUser(callerBase); + + hidden.setVanished(true); + + // Without offline-capable lookup, hidden target should not be found + assertThrows(PlayerNotFoundException.class, + () -> ess.matchUser(server, caller, "Hidden", false, false)); + + // With offline-capable lookup, only exact matches should return the hidden user + assertThrows(PlayerNotFoundException.class, + () -> ess.matchUser(server, caller, "Hid", false, true)); + + final User matched = ess.matchUser(server, caller, "Hidden", false, true); + assertEquals("Hidden", matched.getName()); + } + + @Test + public void hiddenPlayerExactOnlyWhenOfflineLookupConsoleCaller() throws Exception { + final PlayerMock hiddenBase = server.addPlayer("HiddenTwo"); + final User hidden = ess.getUser(hiddenBase); + + hidden.setHidden(true); + + // Console caller represented by null source user + assertThrows(PlayerNotFoundException.class, + () -> ess.matchUser(server, null, "HiddenT", false, true)); + + final User matched = ess.matchUser(server, null, "HiddenTwo", false, true); + assertEquals("HiddenTwo", matched.getName()); + } +} + +